diff options
547 files changed, 11100 insertions, 3723 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 44f3d706a0d0..52200bfcfdf6 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -180,6 +180,18 @@ aconfig_declarations { srcs: ["core/java/android/nfc/*.aconfig"], } +cc_aconfig_library { + name: "android_nfc_flags_aconfig_c_lib", + vendor_available: true, + aconfig_declarations: "android.nfc.flags-aconfig", + apex_available: [ + "//apex_available:platform", + "com.android.nfcservices", + "nfc_nci.st21nfc.default", + ], + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + java_aconfig_library { name: "android.nfc.flags-aconfig-java", aconfig_declarations: "android.nfc.flags-aconfig", diff --git a/Android.bp b/Android.bp index 895ef98c5537..a402c57689d6 100644 --- a/Android.bp +++ b/Android.bp @@ -249,6 +249,7 @@ java_library { "android.se.omapi-V1-java", "android.system.suspend.control.internal-java", "devicepolicyprotosnano", + "ImmutabilityAnnotation", "com.android.sysprop.init", "com.android.sysprop.localization", diff --git a/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 9ce5342bcfd3..75ac8fbed995 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5316,21 +5316,23 @@ package android.app { method public android.net.Uri getConditionId(); method @Nullable public android.content.ComponentName getConfigurationActivity(); method public long getCreationTime(); + method @FlaggedApi("android.app.modes_api") @Nullable public android.service.notification.ZenDeviceEffects getDeviceEffects(); method @FlaggedApi("android.app.modes_api") @DrawableRes public int getIconResId(); method public int getInterruptionFilter(); method public String getName(); method public android.content.ComponentName getOwner(); method @FlaggedApi("android.app.modes_api") @Nullable public String getTriggerDescription(); method @FlaggedApi("android.app.modes_api") public int getType(); - method public android.service.notification.ZenPolicy getZenPolicy(); + method @Nullable public android.service.notification.ZenPolicy getZenPolicy(); method public boolean isEnabled(); method @FlaggedApi("android.app.modes_api") public boolean isManualInvocationAllowed(); method public void setConditionId(android.net.Uri); method public void setConfigurationActivity(@Nullable android.content.ComponentName); + method @FlaggedApi("android.app.modes_api") public void setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects); method public void setEnabled(boolean); method public void setInterruptionFilter(int); method public void setName(String); - method public void setZenPolicy(android.service.notification.ZenPolicy); + method public void setZenPolicy(@Nullable android.service.notification.ZenPolicy); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.AutomaticZenRule> CREATOR; field @FlaggedApi("android.app.modes_api") public static final int TYPE_BEDTIME = 3; // 0x3 @@ -5350,6 +5352,7 @@ package android.app { method @NonNull public android.app.AutomaticZenRule build(); method @NonNull public android.app.AutomaticZenRule.Builder setConditionId(@NonNull android.net.Uri); method @NonNull public android.app.AutomaticZenRule.Builder setConfigurationActivity(@Nullable android.content.ComponentName); + method @NonNull public android.app.AutomaticZenRule.Builder setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects); method @NonNull public android.app.AutomaticZenRule.Builder setEnabled(boolean); method @NonNull public android.app.AutomaticZenRule.Builder setIconResId(@DrawableRes int); method @NonNull public android.app.AutomaticZenRule.Builder setInterruptionFilter(int); @@ -7861,6 +7864,7 @@ package android.app.admin { field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity"; field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken"; field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled"; + field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages"; } @@ -22804,11 +22808,11 @@ package android.media { method public void clearOnSessionLostStateListener(); method public void close(); method public void closeSession(@NonNull byte[]); - method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel(); + method public int getConnectedHdcpLevel(); method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String); method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException; method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages(); - method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel(); + method public int getMaxHdcpLevel(); method public static int getMaxSecurityLevel(); method public int getMaxSessionCount(); method public android.os.PersistableBundle getMetrics(); @@ -22822,13 +22826,13 @@ package android.media { method @Deprecated @NonNull public byte[] getSecureStop(@NonNull byte[]); method @Deprecated @NonNull public java.util.List<byte[]> getSecureStopIds(); method @Deprecated @NonNull public java.util.List<byte[]> getSecureStops(); - method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]); + method public int getSecurityLevel(@NonNull byte[]); method @NonNull public static java.util.List<java.util.UUID> getSupportedCryptoSchemes(); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String); - method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int); + method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, int); method @NonNull public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException; - method @NonNull public byte[] openSession(@android.media.MediaDrm.SecurityLevel int) throws android.media.NotProvisionedException, android.media.ResourceBusyException; + method @NonNull public byte[] openSession(int) throws android.media.NotProvisionedException, android.media.ResourceBusyException; method @Nullable public byte[] provideKeyResponse(@NonNull byte[], @NonNull byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException; method public void provideProvisionResponse(@NonNull byte[]) throws android.media.DeniedByServerException; method @NonNull public java.util.HashMap<java.lang.String,java.lang.String> queryKeyStatus(@NonNull byte[]); @@ -22840,7 +22844,7 @@ package android.media { method public void removeOfflineLicense(@NonNull byte[]); method @Deprecated public void removeSecureStop(@NonNull byte[]); method public boolean requiresSecureDecoder(@NonNull String); - method public boolean requiresSecureDecoder(@NonNull String, @android.media.MediaDrm.SecurityLevel int); + method public boolean requiresSecureDecoder(@NonNull String, int); method public void restoreKeys(@NonNull byte[], @NonNull byte[]); method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener); method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener, @Nullable android.os.Handler); @@ -22929,9 +22933,6 @@ package android.media { field public static final int ERROR_ZERO_SUBSAMPLES = 33; // 0x21 } - @Deprecated @IntDef({android.media.MediaDrm.HDCP_LEVEL_UNKNOWN, android.media.MediaDrm.HDCP_NONE, android.media.MediaDrm.HDCP_V1, android.media.MediaDrm.HDCP_V2, android.media.MediaDrm.HDCP_V2_1, android.media.MediaDrm.HDCP_V2_2, android.media.MediaDrm.HDCP_V2_3, android.media.MediaDrm.HDCP_NO_DIGITAL_OUTPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.HdcpLevel { - } - public static final class MediaDrm.KeyRequest { method @NonNull public byte[] getData(); method @NonNull public String getDefaultUrl(); @@ -23033,9 +23034,6 @@ package android.media { method @NonNull public String getDefaultUrl(); } - @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel { - } - public static final class MediaDrm.SessionException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable { ctor public MediaDrm.SessionException(int, @Nullable String); method @Deprecated public int getErrorCode(); @@ -28698,14 +28696,17 @@ package android.nfc { } public final class NfcAdapter { + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction(); method public void disableForegroundDispatch(android.app.Activity); method public void disableReaderMode(android.app.Activity); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction(); method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]); method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo(); method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler); method public boolean isEnabled(); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported(); method public boolean isSecureNfcEnabled(); @@ -28813,6 +28814,7 @@ package android.nfc.cardemulation { method public boolean removeAidsForService(android.content.ComponentName, String); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String); method public boolean setPreferredService(android.app.Activity, android.content.ComponentName); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean); method public boolean supportsAidPrefixRegistration(); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName); method public boolean unsetPreferredService(android.app.Activity); @@ -28832,9 +28834,20 @@ package android.nfc.cardemulation { method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onDeactivated(int); method public abstract byte[] processCommandApdu(byte[], android.os.Bundle); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>); method public final void sendResponseApdu(byte[]); field public static final int DEACTIVATION_DESELECTED = 1; // 0x1 field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0 + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U' field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE"; field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service"; } @@ -37273,6 +37286,7 @@ package android.provider { } public static final class Telephony.Carriers implements android.provider.BaseColumns { + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String ALWAYS_ON = "always_on"; field public static final String APN = "apn"; field public static final String AUTH_TYPE = "authtype"; field @Deprecated public static final String BEARER = "bearer"; @@ -37286,6 +37300,8 @@ package android.provider { field public static final String MMSPORT = "mmsport"; field public static final String MMSPROXY = "mmsproxy"; field @Deprecated public static final String MNC = "mnc"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String MTU_V4 = "mtu_v4"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String MTU_V6 = "mtu_v6"; field @Deprecated public static final String MVNO_MATCH_DATA = "mvno_match_data"; field @Deprecated public static final String MVNO_TYPE = "mvno_type"; field public static final String NAME = "name"; @@ -37301,6 +37317,8 @@ package android.provider { field public static final String SUBSCRIPTION_ID = "sub_id"; field public static final String TYPE = "type"; field public static final String USER = "user"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String USER_EDITABLE = "user_editable"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String USER_VISIBLE = "user_visible"; } public static final class Telephony.Mms implements android.provider.Telephony.BaseMmsColumns { @@ -40697,6 +40715,26 @@ package android.service.notification { field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.StatusBarNotification> CREATOR; } + @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable { + method public int describeContents(); + method public boolean shouldDimWallpaper(); + method public boolean shouldDisplayGrayscale(); + method public boolean shouldSuppressAmbientDisplay(); + method public boolean shouldUseNightMode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.ZenDeviceEffects> CREATOR; + } + + @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder { + ctor public ZenDeviceEffects.Builder(); + ctor public ZenDeviceEffects.Builder(@NonNull android.service.notification.ZenDeviceEffects); + method @NonNull public android.service.notification.ZenDeviceEffects build(); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDimWallpaper(boolean); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDisplayGrayscale(boolean); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldSuppressAmbientDisplay(boolean); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldUseNightMode(boolean); + } + public final class ZenPolicy implements android.os.Parcelable { method public int describeContents(); method public int getPriorityCallSenders(); @@ -43651,6 +43689,7 @@ package android.telephony { field public static final String KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT = "iwlan.child_sa_rekey_soft_timer_sec_int"; field public static final String KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_cbc_key_size_int_array"; field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_gcm_key_size_int_array"; field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array"; field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int"; field public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT = "iwlan.epdg_address_ip_type_preference_int"; @@ -43666,12 +43705,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"; @@ -45817,6 +45859,7 @@ package android.telephony.data { method public int getProxyPort(); method public int getRoamingProtocol(); method public String getUser(); + method @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public boolean isAlwaysOn(); method public boolean isEnabled(); method public boolean isPersistent(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -45856,6 +45899,7 @@ package android.telephony.data { public static class ApnSetting.Builder { ctor public ApnSetting.Builder(); method public android.telephony.data.ApnSetting build(); + method @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") @NonNull public android.telephony.data.ApnSetting.Builder setAlwaysOn(boolean); method @NonNull public android.telephony.data.ApnSetting.Builder setApnName(@Nullable String); method @NonNull public android.telephony.data.ApnSetting.Builder setApnTypeBitmask(int); method @NonNull public android.telephony.data.ApnSetting.Builder setAuthType(int); diff --git a/core/api/removed.txt b/core/api/removed.txt index 5a4be65ef559..989bb7756e4c 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -224,6 +224,12 @@ package android.media { ctor public AudioFormat(); } + @Deprecated @IntDef({android.media.MediaDrm.HDCP_LEVEL_UNKNOWN, android.media.MediaDrm.HDCP_NONE, android.media.MediaDrm.HDCP_V1, android.media.MediaDrm.HDCP_V2, android.media.MediaDrm.HDCP_V2_1, android.media.MediaDrm.HDCP_V2_2, android.media.MediaDrm.HDCP_V2_3, android.media.MediaDrm.HDCP_NO_DIGITAL_OUTPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.HdcpLevel { + } + + @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel { + } + } package android.media.tv { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index de07fdde6267..fbd2142ba625 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3185,6 +3185,7 @@ package android.companion.virtual { field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2 field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1 field public static final int LAUNCH_SUCCESS = 0; // 0x0 + field @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") public static final String PERSISTENT_DEVICE_ID_DEFAULT = "default:0"; } public static interface VirtualDeviceManager.ActivityListener { @@ -3330,6 +3331,53 @@ package android.companion.virtual.audio { } +package android.companion.virtual.camera { + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCamera implements java.io.Closeable { + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @NonNull public android.companion.virtual.camera.VirtualCameraConfig getConfig(); + } + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback { + method public void onProcessCaptureRequest(int, long, @Nullable android.companion.virtual.camera.VirtualCameraMetadata); + method public void onStreamClosed(int); + method public void onStreamConfigured(int, @NonNull android.view.Surface, @NonNull android.companion.virtual.camera.VirtualCameraStreamConfig); + } + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable { + method public int describeContents(); + method @StringRes public int getDisplayNameStringRes(); + method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR; + } + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder { + ctor public VirtualCameraConfig.Builder(); + method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int); + method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build(); + method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setDisplayNameStringRes(@StringRes int); + method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback); + } + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraMetadata implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraMetadata> CREATOR; + } + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable { + ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int); + method public int describeContents(); + method public int getFormat(); + method @IntRange(from=1) public int getHeight(); + method @IntRange(from=1) public int getWidth(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraStreamConfig> CREATOR; + } + +} + package android.companion.virtual.sensor { public final class VirtualSensor implements android.os.Parcelable { @@ -4762,7 +4810,7 @@ package android.hardware.hdmi { method public void onChange(@NonNull String); } - @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult { + @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface HdmiControlManager.ControlCallbackResult { } public static interface HdmiControlManager.HotplugEventListener { @@ -11234,15 +11282,11 @@ package android.provider { field public static final String MAX_CONNECTIONS = "max_conns"; field public static final String MODEM_PERSIST = "modem_cognitive"; field @Deprecated public static final String MTU = "mtu"; - field public static final String MTU_V4 = "mtu_v4"; - field public static final String MTU_V6 = "mtu_v6"; field public static final int NO_APN_SET_ID = 0; // 0x0 field public static final String TIME_LIMIT_FOR_MAX_CONNECTIONS = "max_conns_time"; field public static final int UNEDITED = 0; // 0x0 field public static final int USER_DELETED = 2; // 0x2 - field public static final String USER_EDITABLE = "user_editable"; field public static final int USER_EDITED = 1; // 0x1 - field public static final String USER_VISIBLE = "user_visible"; field public static final String WAIT_TIME_RETRY = "wait_time"; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 8b20720418b6..75797edfb218 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -541,7 +541,6 @@ package android.app.admin { field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods"; field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended"; field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled"; - field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; } public class DevicePolicyManager { diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 3f9cc65ba1db..8ad6ea207665 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -632,6 +632,7 @@ public class AccessibilityServiceInfo implements Parcelable { InputDevice.SOURCE_JOYSTICK, InputDevice.SOURCE_SENSOR }) + @Retention(RetentionPolicy.SOURCE) public @interface MotionEventSources {} /** diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java index af00f316de7b..6ec956ee7783 100644 --- a/core/java/android/accessibilityservice/TouchInteractionController.java +++ b/core/java/android/accessibilityservice/TouchInteractionController.java @@ -24,6 +24,8 @@ import android.util.ArrayMap; import android.view.MotionEvent; import android.view.accessibility.AccessibilityInteractionClient; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Executor; @@ -92,6 +94,7 @@ public final class TouchInteractionController { STATE_DRAGGING, STATE_DELEGATING }) + @Retention(RetentionPolicy.SOURCE) private @interface State {} // The maximum number of pointers that can be touching the screen at once. (See MAX_POINTER_ID diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index be433d2ab0f2..ed18d81d7914 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -23,7 +23,9 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.os.Process.myUid; + import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; + import static java.lang.Character.MIN_VALUE; import android.annotation.AnimRes; @@ -1000,6 +1002,7 @@ public class Activity extends ContextThemeWrapper FULLSCREEN_MODE_REQUEST_EXIT, FULLSCREEN_MODE_REQUEST_ENTER }) + @Retention(RetentionPolicy.SOURCE) public @interface FullscreenModeRequest {} /** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request exiting the @@ -1016,6 +1019,7 @@ public class Activity extends ContextThemeWrapper OVERRIDE_TRANSITION_OPEN, OVERRIDE_TRANSITION_CLOSE }) + @Retention(RetentionPolicy.SOURCE) public @interface OverrideTransition {} /** diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 8b4ebaee04c5..854e12177fb5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4998,6 +4998,7 @@ public class ActivityManager { STOP_USER_ON_SWITCH_TRUE, STOP_USER_ON_SWITCH_FALSE }) + @Retention(RetentionPolicy.SOURCE) public @interface StopUserOnSwitch {} /** diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 919e084002ea..a7b29aab4e69 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -22,12 +22,12 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.NotificationManager.InterruptionFilter; -import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.service.notification.Condition; +import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenPolicy; import android.view.WindowInsetsController; @@ -111,6 +111,7 @@ public final class AutomaticZenRule implements Parcelable { private ComponentName configurationActivity; private long creationTime; private ZenPolicy mZenPolicy; + private ZenDeviceEffects mDeviceEffects; private boolean mModified = false; private String mPkg; private int mType = TYPE_UNKNOWN; @@ -190,6 +191,7 @@ public final class AutomaticZenRule implements Parcelable { /** * @hide */ + // TODO: b/310620812 - Remove when the flag is inlined (all system callers should use Builder). public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity, Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled, long creationTime) { @@ -209,10 +211,11 @@ public final class AutomaticZenRule implements Parcelable { configurationActivity = getTrimmedComponentName( source.readParcelable(null, android.content.ComponentName.class)); creationTime = source.readLong(); - mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); + mZenPolicy = source.readParcelable(null, ZenPolicy.class); mModified = source.readInt() == ENABLED; mPkg = source.readString(); if (Flags.modesApi()) { + mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class); mAllowManualInvocation = source.readBoolean(); mIconResId = source.readInt(); mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH); @@ -274,10 +277,18 @@ public final class AutomaticZenRule implements Parcelable { /** * Gets the zen policy. */ + @Nullable public ZenPolicy getZenPolicy() { return mZenPolicy == null ? null : this.mZenPolicy.copy(); } + /** Gets the {@link ZenDeviceEffects} of this rule. */ + @Nullable + @FlaggedApi(Flags.FLAG_MODES_API) + public ZenDeviceEffects getDeviceEffects() { + return mDeviceEffects; + } + /** * Returns the time this rule was created, represented as milliseconds since the epoch. */ @@ -325,11 +336,21 @@ public final class AutomaticZenRule implements Parcelable { /** * Sets the zen policy. */ - public void setZenPolicy(ZenPolicy zenPolicy) { + public void setZenPolicy(@Nullable ZenPolicy zenPolicy) { this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy()); } /** + * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes to + * the device behavior that should apply while the rule is active, but are not directly related + * to suppressing notifications (for example: disabling always-on display). + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public void setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) { + mDeviceEffects = deviceEffects; + } + + /** * Sets the configuration activity - an activity that handles * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information * about this rule and/or allows them to configure it. This is required to be non-null for rules @@ -451,6 +472,7 @@ public final class AutomaticZenRule implements Parcelable { dest.writeInt(mModified ? ENABLED : DISABLED); dest.writeString(mPkg); if (Flags.modesApi()) { + dest.writeParcelable(mDeviceEffects, 0); dest.writeBoolean(mAllowManualInvocation); dest.writeInt(mIconResId); dest.writeString(mTriggerDescription); @@ -472,7 +494,8 @@ public final class AutomaticZenRule implements Parcelable { .append(",mZenPolicy=").append(mZenPolicy); if (Flags.modesApi()) { - sb.append(",allowManualInvocation=").append(mAllowManualInvocation) + sb.append(",deviceEffects=").append(mDeviceEffects) + .append(",allowManualInvocation=").append(mAllowManualInvocation) .append(",iconResId=").append(mIconResId) .append(",triggerDescription=").append(mTriggerDescription) .append(",type=").append(mType); @@ -498,6 +521,7 @@ public final class AutomaticZenRule implements Parcelable { && other.creationTime == creationTime; if (Flags.modesApi()) { return finalEquals + && Objects.equals(other.mDeviceEffects, mDeviceEffects) && other.mAllowManualInvocation == mAllowManualInvocation && other.mIconResId == mIconResId && Objects.equals(other.mTriggerDescription, mTriggerDescription) @@ -510,8 +534,8 @@ public final class AutomaticZenRule implements Parcelable { public int hashCode() { if (Flags.modesApi()) { return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, - configurationActivity, mZenPolicy, mModified, creationTime, mPkg, - mAllowManualInvocation, mIconResId, mTriggerDescription, mType); + configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime, + mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType); } return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, configurationActivity, mZenPolicy, mModified, creationTime, mPkg); @@ -573,6 +597,7 @@ public final class AutomaticZenRule implements Parcelable { private boolean mEnabled; private ComponentName mConfigurationActivity = null; private ZenPolicy mPolicy = null; + private ZenDeviceEffects mDeviceEffects = null; private int mType; private String mDescription; private int mIconResId; @@ -588,6 +613,7 @@ public final class AutomaticZenRule implements Parcelable { mEnabled = rule.isEnabled(); mConfigurationActivity = rule.getConfigurationActivity(); mPolicy = rule.getZenPolicy(); + mDeviceEffects = rule.getDeviceEffects(); mType = rule.getType(); mDescription = rule.getTriggerDescription(); mIconResId = rule.getIconResId(); @@ -639,6 +665,17 @@ public final class AutomaticZenRule implements Parcelable { } /** + * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes + * to the device behavior that should apply while the rule is active, but are not directly + * related to suppressing notifications (for example: disabling always-on display). + */ + @NonNull + public Builder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) { + mDeviceEffects = deviceEffects; + return this; + } + + /** * Sets the type of the rule */ public @NonNull Builder setType(@Type int type) { @@ -687,6 +724,7 @@ public final class AutomaticZenRule implements Parcelable { public @NonNull AutomaticZenRule build() { AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity, mConditionId, mPolicy, mInterruptionFilter, mEnabled); + rule.mDeviceEffects = mDeviceEffects; rule.creationTime = mCreationTime; rule.mType = mType; rule.mTriggerDescription = mDescription; diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index ec5effd0963d..94385717d349 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -214,6 +214,8 @@ interface INotificationManager void setNotificationPolicyAccessGranted(String pkg, boolean granted); void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted); AutomaticZenRule getAutomaticZenRule(String id); + Map<String, AutomaticZenRule> getAutomaticZenRules(); + // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined. List<ZenModeConfig.ZenRule> getZenRules(); String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg); boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 545ba8e81f62..6aad1682466d 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -64,6 +64,8 @@ import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.PasswordValidationError; import com.android.internal.widget.VerifyCredentialResponse; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; @@ -235,6 +237,7 @@ public class KeyguardManager { PIN, PATTERN }) + @Retention(RetentionPolicy.SOURCE) @interface LockTypes {} private final IKeyguardLockedStateListener mIKeyguardLockedStateListener = diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2d80b1ffca6c..337e3f1195be 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -924,6 +924,7 @@ public class Notification implements Parcelable VISIBILITY_SECRET, NotificationManager.VISIBILITY_NO_OVERRIDE }) + @Retention(RetentionPolicy.SOURCE) public @interface NotificationVisibilityOverride{}; /** diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index ffb79b3fc552..51c937d4e94a 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1256,17 +1256,21 @@ public class NotificationManager { public Map<String, AutomaticZenRule> getAutomaticZenRules() { INotificationManager service = getService(); try { - List<ZenModeConfig.ZenRule> rules = service.getZenRules(); - Map<String, AutomaticZenRule> ruleMap = new HashMap<>(); - for (ZenModeConfig.ZenRule rule : rules) { - AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, - rule.configurationActivity, rule.conditionId, rule.zenPolicy, - zenModeToInterruptionFilter(rule.zenMode), rule.enabled, - rule.creationTime); - azr.setPackageName(rule.pkg); - ruleMap.put(rule.id, azr); + if (Flags.modesApi()) { + return service.getAutomaticZenRules(); + } else { + List<ZenModeConfig.ZenRule> rules = service.getZenRules(); + Map<String, AutomaticZenRule> ruleMap = new HashMap<>(); + for (ZenModeConfig.ZenRule rule : rules) { + AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, + rule.configurationActivity, rule.conditionId, rule.zenPolicy, + zenModeToInterruptionFilter(rule.zenMode), rule.enabled, + rule.creationTime); + azr.setPackageName(rule.pkg); + ruleMap.put(rule.id, azr); + } + return ruleMap; } - return ruleMap; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index fa52968e4554..79a5879b5cc0 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -113,6 +113,7 @@ import android.hardware.iris.IrisManager; import android.hardware.lights.LightsManager; import android.hardware.lights.SystemLightsManager; import android.hardware.location.ContextHubManager; +import android.hardware.location.IContextHubService; import android.hardware.radio.RadioManager; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbManager; @@ -1118,8 +1119,12 @@ public final class SystemServiceRegistry { new CachedServiceFetcher<ContextHubManager>() { @Override public ContextHubManager createService(ContextImpl ctx) throws ServiceNotFoundException { - return new ContextHubManager(ctx.getOuterContext(), - ctx.mMainThread.getHandler().getLooper()); + IBinder b = ServiceManager.getService(Context.CONTEXTHUB_SERVICE); + if (b == null) { + return null; + } + return new ContextHubManager(IContextHubService.Stub.asInterface(b), + ctx.mMainThread.getHandler().getLooper()); }}); registerService(Context.INCIDENT_SERVICE, IncidentManager.class, diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 019a1a8cb2a6..46216343dcff 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -42,6 +42,8 @@ import android.view.Surface; import android.view.WindowManager; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -120,6 +122,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu WINDOWING_MODE_PINNED, WINDOWING_MODE_FREEFORM, }) + @Retention(RetentionPolicy.SOURCE) public @interface WindowingMode {} /** The current activity type of the configuration. */ @@ -147,6 +150,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_DREAM, }) + @Retention(RetentionPolicy.SOURCE) public @interface ActivityType {} /** The current always on top status of the configuration. */ diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index e4ee959336a5..14462b853c02 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -47,6 +47,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; @@ -175,6 +177,7 @@ public final class DeviceAdminInfo implements Parcelable { public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED}) + @Retention(RetentionPolicy.SOURCE) private @interface HeadlessDeviceOwnerMode {} /** @hide */ diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index 84b1ca5c6a61..b0bec783555b 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -164,11 +164,8 @@ public final class DevicePolicyIdentifiers { /** * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}. - * - * @hide */ @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED) - @TestApi public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3df11f6f5691..3ee9d69229eb 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3998,8 +3998,7 @@ public class DevicePolicyManager { /** * An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which - * resource IDs (see {@link DevicePolicyResources.Drawables} and - * {@link DevicePolicyResources.Strings}) have been updated. + * resource IDs (i.e. strings and drawables) have been updated. */ public static final String EXTRA_RESOURCE_IDS = "android.app.extra.RESOURCE_IDS"; @@ -8370,9 +8369,7 @@ public class DevicePolicyManager { * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was * successfully set or not. This callback will contain: * <ul> - * <li> The policy identifier returned from - * {@link DevicePolicyIdentifiers#getIdentifierForUserRestriction(String)} with user restriction - * {@link UserManager#DISALLOW_CAMERA} + * <li> The policy identifier: userRestriction_no_camera * <li> The {@link TargetUser} that this policy relates to * <li> The {@link PolicyUpdateResult}, which will be * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the @@ -13403,7 +13400,7 @@ public class DevicePolicyManager { * A device owner, by default, may continue granting these permissions. However, for increased * user control, the admin may opt out of controlling grants for these permissions by including * {@link #EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT} in the provisioning parameters. - * In that case the device owner's control will be limited do denying these permissions. + * In that case the device owner's control will be limited to denying these permissions. * <p> * NOTE: On devices running {@link android.os.Build.VERSION_CODES#S} and above, control over * the following permissions are restricted for managed profile owners: @@ -17113,19 +17110,19 @@ public class DevicePolicyManager { * Returns {@code true} if this device is marked as a financed device. * * <p>A financed device can be entered into lock task mode (see {@link #setLockTaskPackages}) - * by the holder of the role {@link android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}. + * by the holder of the role {@code android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}. * If this occurs, Device Owners and Profile Owners that have set lock task packages or * features, or that attempt to set lock task packages or features, will receive a callback * indicating that it could not be set. See {@link PolicyUpdateReceiver#onPolicyChanged} and * {@link PolicyUpdateReceiver#onPolicySetResult}. * * <p>To be informed of changes to this status you can subscribe to the broadcast - * {@link ACTION_DEVICE_FINANCING_STATE_CHANGED}. + * {@link #ACTION_DEVICE_FINANCING_STATE_CHANGED}. * * @throws SecurityException if the caller is not a device owner, profile owner of an * organization-owned managed profile, profile owner on the primary user or holder of one of the - * following roles: {@link android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT}, - * android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION. + * following roles: {@code android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT}, + * {@code android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION}. */ public boolean isDeviceFinanced() { throwIfParentInstance("isDeviceFinanced"); diff --git a/core/java/android/app/admin/DevicePolicyResourcesManager.java b/core/java/android/app/admin/DevicePolicyResourcesManager.java index 2cc189f87ced..7a7123167771 100644 --- a/core/java/android/app/admin/DevicePolicyResourcesManager.java +++ b/core/java/android/app/admin/DevicePolicyResourcesManager.java @@ -452,7 +452,7 @@ public class DevicePolicyResourcesManager { /** * Returns the appropriate updated string for the {@code stringId} (see - * {@link DevicePolicyResources.Strings}) if one was set using + * {@code DevicePolicyResources.Strings}) if one was set using * {@code setStrings}, otherwise returns the string from {@code defaultStringLoader}. * * <p>Also returns the string from {@code defaultStringLoader} if {@code stringId} is diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java index a6595feed1f7..b5c66ffa72a1 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEvent.java +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java @@ -86,7 +86,9 @@ public final class AmbientContextEvent implements Parcelable { EVENT_SNORE, EVENT_BACK_DOUBLE_TAP, EVENT_VENDOR_WEARABLE_START, - }) public @interface EventCode {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EventCode {} /** The integer indicating an unknown level. */ public static final int LEVEL_UNKNOWN = 0; @@ -114,7 +116,9 @@ public final class AmbientContextEvent implements Parcelable { LEVEL_MEDIUM, LEVEL_MEDIUM_HIGH, LEVEL_HIGH - }) public @interface LevelValue {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LevelValue {} @EventCode private final int mEventType; private static int defaultEventType() { diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java index bf383f1165c4..159481f82e39 100644 --- a/core/java/android/app/ambientcontext/AmbientContextManager.java +++ b/core/java/android/app/ambientcontext/AmbientContextManager.java @@ -32,6 +32,8 @@ import android.os.RemoteException; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -106,7 +108,9 @@ public final class AmbientContextManager { STATUS_SERVICE_UNAVAILABLE, STATUS_MICROPHONE_DISABLED, STATUS_ACCESS_DENIED - }) public @interface StatusCode {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StatusCode {} /** * Allows clients to retrieve the list of {@link AmbientContextEvent}s from the intent. diff --git a/core/java/android/app/cloudsearch/SearchResponse.java b/core/java/android/app/cloudsearch/SearchResponse.java index c86142e0d22d..dab1657d60d0 100644 --- a/core/java/android/app/cloudsearch/SearchResponse.java +++ b/core/java/android/app/cloudsearch/SearchResponse.java @@ -21,6 +21,8 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -37,6 +39,7 @@ public final class SearchResponse implements Parcelable { SEARCH_STATUS_OK, SEARCH_STATUS_TIME_OUT, SEARCH_STATUS_NO_INTERNET}) + @Retention(RetentionPolicy.SOURCE) public @interface SearchStatusCode { } diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java index dd332c850d5d..bc8fac5fa0ce 100644 --- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java @@ -71,17 +71,13 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { @NonNull public static ActivityConfigurationChangeItem obtain(@NonNull IBinder activityToken, @NonNull Configuration config) { - if (config == null) { - throw new IllegalArgumentException("Config must not be null."); - } - ActivityConfigurationChangeItem instance = ObjectPool.obtain(ActivityConfigurationChangeItem.class); if (instance == null) { instance = new ActivityConfigurationChangeItem(); } instance.setActivityToken(activityToken); - instance.mConfiguration = config; + instance.mConfiguration = new Configuration(config); return instance; } @@ -89,7 +85,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { @Override public void recycle() { super.recycle(); - mConfiguration = Configuration.EMPTY; + mConfiguration = null; ObjectPool.recycle(this); } diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java index a5dd115d78b3..3ce094ef7467 100644 --- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java +++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java @@ -32,6 +32,7 @@ import android.util.Slog; import com.android.internal.content.ReferrerIntent; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -51,7 +52,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem { /** * A record that was properly configured for relaunch. Execution will be cancelled if not - * initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}. + * initialized after {@link #preExecute(ClientTransactionHandler)}. */ private ActivityClientRecord mActivityClientRecord; @@ -99,10 +100,11 @@ public class ActivityRelaunchItem extends ActivityTransactionItem { instance = new ActivityRelaunchItem(); } instance.setActivityToken(activityToken); - instance.mPendingResults = pendingResults; - instance.mPendingNewIntents = pendingNewIntents; + instance.mPendingResults = pendingResults != null ? new ArrayList<>(pendingResults) : null; + instance.mPendingNewIntents = + pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null; instance.mConfigChanges = configChanges; - instance.mConfig = config; + instance.mConfig = new MergedConfiguration(config); instance.mPreserveWindow = preserveWindow; return instance; diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java index 24fced4981d6..51a09fb59236 100644 --- a/core/java/android/app/servertransaction/ActivityResultItem.java +++ b/core/java/android/app/servertransaction/ActivityResultItem.java @@ -35,6 +35,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Trace; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -82,7 +83,7 @@ public class ActivityResultItem extends ActivityTransactionItem { instance = new ActivityResultItem(); } instance.setActivityToken(activityToken); - instance.mResultInfoList = resultInfoList; + instance.mResultInfoList = new ArrayList<>(resultInfoList); return instance; } diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java index 2a65b3528145..b4ff476f4702 100644 --- a/core/java/android/app/servertransaction/ActivityTransactionItem.java +++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java @@ -20,6 +20,8 @@ import static android.app.servertransaction.TransactionExecutorHelper.getActivit import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; +import static java.util.Objects.requireNonNull; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -93,7 +95,7 @@ public abstract class ActivityTransactionItem extends ClientTransactionItem { } void setActivityToken(@NonNull IBinder activityToken) { - mActivityToken = activityToken; + mActivityToken = requireNonNull(activityToken); } // To be overridden diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 9c0cd39e8102..7c34cdefe95f 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -54,12 +54,14 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** A list of individual callbacks to a client. */ @UnsupportedAppUsage + @Nullable private List<ClientTransactionItem> mActivityCallbacks; /** * Final lifecycle state in which the client activity should be after the transaction is * executed. */ + @Nullable private ActivityLifecycleItem mLifecycleStateRequest; /** Target client. */ @@ -123,6 +125,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { @VisibleForTesting(visibility = PACKAGE) @UnsupportedAppUsage @Deprecated + @Nullable public ActivityLifecycleItem getLifecycleStateRequest() { return mLifecycleStateRequest; } @@ -207,7 +210,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { for (int i = 0; i < size; i++) { mActivityCallbacks.get(i).recycle(); } - mActivityCallbacks.clear(); + mActivityCallbacks = null; } if (mLifecycleStateRequest != null) { mLifecycleStateRequest.recycle(); diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java index 96961aced987..0e327a7627d1 100644 --- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java @@ -64,7 +64,7 @@ public class ConfigurationChangeItem extends ClientTransactionItem { if (instance == null) { instance = new ConfigurationChangeItem(); } - instance.mConfiguration = config; + instance.mConfiguration = new Configuration(config); instance.mDeviceId = deviceId; return instance; diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index a64c744c70ba..d2ef65aec698 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -45,6 +45,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -135,10 +136,16 @@ public class LaunchActivityItem extends ClientTransactionItem { if (instance == null) { instance = new LaunchActivityItem(); } - setValues(instance, activityToken, intent, ident, info, curConfig, overrideConfig, deviceId, - referrer, voiceInteractor, procState, state, persistentState, pendingResults, - pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken, - activityClientController, shareableActivityToken, + setValues(instance, activityToken, new Intent(intent), ident, new ActivityInfo(info), + new Configuration(curConfig), new Configuration(overrideConfig), deviceId, + referrer, voiceInteractor, procState, + state != null ? new Bundle(state) : null, + persistentState != null ? new PersistableBundle(persistentState) : null, + pendingResults != null ? new ArrayList<>(pendingResults) : null, + pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null, + activityOptions, isForward, + profilerInfo != null ? new ProfilerInfo(profilerInfo) : null, + assistToken, activityClientController, shareableActivityToken, launchedFromBubble, taskFragmentToken); return instance; diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java index e56d3f862b1b..961da19daeed 100644 --- a/core/java/android/app/servertransaction/MoveToDisplayItem.java +++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java @@ -69,7 +69,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem { } instance.setActivityToken(activityToken); instance.mTargetDisplayId = targetDisplayId; - instance.mConfiguration = configuration; + instance.mConfiguration = new Configuration(configuration); return instance; } @@ -78,7 +78,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem { public void recycle() { super.recycle(); mTargetDisplayId = 0; - mConfiguration = Configuration.EMPTY; + mConfiguration = null; ObjectPool.recycle(this); } diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java index 8e995aa05a48..acf2ea429e82 100644 --- a/core/java/android/app/servertransaction/NewIntentItem.java +++ b/core/java/android/app/servertransaction/NewIntentItem.java @@ -32,6 +32,7 @@ import android.os.Trace; import com.android.internal.content.ReferrerIntent; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -71,7 +72,7 @@ public class NewIntentItem extends ActivityTransactionItem { instance = new NewIntentItem(); } instance.setActivityToken(activityToken); - instance.mIntents = intents; + instance.mIntents = new ArrayList<>(intents); instance.mResume = resume; return instance; diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java index 375d1bf57174..cbad92ff3f38 100644 --- a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java +++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java @@ -65,7 +65,7 @@ public class WindowContextInfoChangeItem extends ClientTransactionItem { instance = new WindowContextInfoChangeItem(); } instance.mClientToken = requireNonNull(clientToken); - instance.mInfo = new WindowContextInfo(config, displayId); + instance.mInfo = new WindowContextInfo(new Configuration(config), displayId); return instance; } diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java index 98281338872b..7d3eb8783c04 100644 --- a/core/java/android/app/servertransaction/WindowStateResizeItem.java +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -77,10 +77,10 @@ public class WindowStateResizeItem extends ClientTransactionItem { instance = new WindowStateResizeItem(); } instance.mWindow = requireNonNull(window); - instance.mFrames = requireNonNull(frames); + instance.mFrames = new ClientWindowFrames(frames); instance.mReportDraw = reportDraw; - instance.mConfiguration = requireNonNull(configuration); - instance.mInsetsState = requireNonNull(insetsState); + instance.mConfiguration = new MergedConfiguration(configuration); + instance.mInsetsState = new InsetsState(insetsState); instance.mForceLayout = forceLayout; instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; instance.mDisplayId = displayId; diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java index d0b4fe473148..f1ca0864a6dd 100644 --- a/core/java/android/app/wearable/WearableSensingManager.java +++ b/core/java/android/app/wearable/WearableSensingManager.java @@ -35,6 +35,8 @@ import android.os.SharedMemory; import android.service.wearable.WearableSensingService; import android.system.OsConstants; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -105,7 +107,9 @@ public class WearableSensingManager { STATUS_SERVICE_UNAVAILABLE, STATUS_WEARABLE_UNAVAILABLE, STATUS_ACCESS_DENIED - }) public @interface StatusCode {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StatusCode {} private final Context mContext; private final IWearableSensingManager mService; diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 9ea3dfc61ef1..e0ce917fa3b3 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -33,6 +33,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UserHandleAware; +import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -795,9 +796,19 @@ public final class CompanionDeviceManager { @UserHandleAware @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public @NonNull List<AssociationInfo> getAllAssociations() { + return getAllAssociations(mContext.getUserId()); + } + + /** + * Per-user version of {@link #getAllAssociations()}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) + public @NonNull List<AssociationInfo> getAllAssociations(@UserIdInt int userId) { if (!checkFeaturePresent()) return Collections.emptyList(); try { - return mService.getAllAssociationsForUser(mContext.getUserId()); + return mService.getAllAssociationsForUser(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -830,12 +841,25 @@ public final class CompanionDeviceManager { @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public void addOnAssociationsChangedListener( @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener) { + addOnAssociationsChangedListener(executor, listener, mContext.getUserId()); + } + + /** + * Per-user version of + * {@link #addOnAssociationsChangedListener(Executor, OnAssociationsChangedListener)}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) + public void addOnAssociationsChangedListener( + @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener, + @UserIdInt int userId) { if (!checkFeaturePresent()) return; synchronized (mListeners) { final OnAssociationsChangedListenerProxy proxy = new OnAssociationsChangedListenerProxy( executor, listener); try { - mService.addOnAssociationsChangedListener(proxy, mContext.getUserId()); + mService.addOnAssociationsChangedListener(proxy, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 2f97080901f9..102cbf3a7e31 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -23,8 +23,7 @@ import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; -import android.companion.virtual.camera.IVirtualCamera; -import android.companion.virtual.camera.VirtualCameraHalConfig; +import android.companion.virtual.camera.VirtualCameraConfig; import android.content.ComponentName; import android.content.IntentFilter; import android.graphics.Point; @@ -236,8 +235,15 @@ interface IVirtualDevice { void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor); /** - * Creates a new VirtualCamera and registers it with the VirtualCameraProvider. + * Creates a new virtual camera and registers it with the virtual camera service. */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") - void registerVirtualCamera(in IVirtualCamera camera); + void registerVirtualCamera(in VirtualCameraConfig camera); + + /** + * Destroys the virtual camera with given config and unregisters it from the virtual camera + * service. + */ + @EnforcePermission("CREATE_VIRTUAL_DEVICE") + void unregisterVirtualCamera(in VirtualCameraConfig camera); } diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java index 93a3e7822888..d0c8be6ca896 100644 --- a/core/java/android/companion/virtual/VirtualDevice.java +++ b/core/java/android/companion/virtual/VirtualDevice.java @@ -32,9 +32,8 @@ import android.os.RemoteException; * Details of a particular virtual device. * * <p>Read-only device representation exposing the properties of an existing virtual device. - * - * @see VirtualDeviceManager#registerVirtualDeviceListener */ +// TODO(b/310912420): Link to VirtualDeviceManager#registerVirtualDeviceListener from the docs public final class VirtualDevice implements Parcelable { private final @NonNull IVirtualDevice mVirtualDevice; @@ -92,8 +91,8 @@ public final class VirtualDevice implements Parcelable { * per device. * * @see Context#createDeviceContext - * @see #getPersistentDeviceId */ + // TODO(b/310912420): Link to #getPersistentDeviceId from the docs public int getDeviceId() { return mId; } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index b3ea93bb8a85..41c90b96dc84 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -149,6 +149,19 @@ public final class VirtualDeviceManager { @SystemApi public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; + /** + * Persistent device identifier corresponding to the default device. + * + * @see Context#DEVICE_ID_DEFAULT + * @see VirtualDevice#getPersistentDeviceId() + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) + public static final String PERSISTENT_DEVICE_ID_DEFAULT = + "default:" + Context.DEVICE_ID_DEFAULT; + private final IVirtualDeviceManager mService; private final Context mContext; @@ -199,9 +212,10 @@ public final class VirtualDeviceManager { * existing virtual devices.</p> * * <p>Note that if a virtual device is closed and becomes invalid, the returned objects will - * not be updated and may contain stale values. Use a {@link VirtualDeviceListener} for real - * time updates of the availability of virtual devices.</p> + * not be updated and may contain stale values.</p> */ + // TODO(b/310912420): Add "Use a VirtualDeviceListener for real time updates of the + // availability of virtual devices." in the note paragraph above with a link annotation. @NonNull public List<android.companion.virtual.VirtualDevice> getVirtualDevices() { if (mService == null) { diff --git a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl b/core/java/android/companion/virtual/camera/IVirtualCamera.aidl deleted file mode 100644 index 58b850dac41c..000000000000 --- a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl +++ /dev/null @@ -1,17 +0,0 @@ -package android.companion.virtual.camera; - -import android.companion.virtual.camera.IVirtualCameraSession; -import android.companion.virtual.camera.VirtualCameraHalConfig; - -/** - * Counterpart of ICameraDevice for virtual camera. - * - * @hide - */ -interface IVirtualCamera { - - IVirtualCameraSession open(); - - VirtualCameraHalConfig getHalConfig(); - -}
\ No newline at end of file diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl new file mode 100644 index 000000000000..fac44b50024f --- /dev/null +++ b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.companion.virtual.camera; + +import android.companion.virtual.camera.VirtualCameraStreamConfig; +import android.companion.virtual.camera.VirtualCameraMetadata; +import android.view.Surface; + +/** + * Interface for the virtual camera service and system server to talk back to the virtual camera owner. + * + * @hide + */ +interface IVirtualCameraCallback { + + /** + * Called when one of the requested stream has been configured by the virtual camera service and + * is ready to receive data onto its {@link Surface} + * + * @param streamId The id of the configured stream + * @param surface The surface to write data into for this stream + * @param streamConfig The image data configuration for this stream + */ + oneway void onStreamConfigured( + int streamId, + in Surface surface, + in VirtualCameraStreamConfig streamConfig); + + /** + * The client application is requesting a camera frame for the given streamId with the provided + * metadata. + * + * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to + * this stream that was provided during the {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} call. + * + * @param streamId The streamId for which the frame is requested. This corresponds to the + * streamId that was given in {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} + * @param frameId The frameId that is being requested. Each request will have a different + * frameId, that will be increasing for each call with a particular streamId. + * @param metadata The metadata requested for the frame. The virtual camera should do its best + * to honor the requested metadata. + */ + oneway void onProcessCaptureRequest( + int streamId, long frameId, in VirtualCameraMetadata metadata); + + /** + * The stream previously configured when {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be + * freed. The Surface was disposed on the client side and should not be used anymore by the virtual camera owner + * + * @param streamId The id of the stream that was closed. + */ + oneway void onStreamClosed(int streamId); + +}
\ No newline at end of file diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java index 791bf0a1ff1f..beee86fcfac2 100644 --- a/core/java/android/companion/virtual/camera/VirtualCamera.java +++ b/core/java/android/companion/virtual/camera/VirtualCamera.java @@ -16,20 +16,44 @@ package android.companion.virtual.camera; +import android.annotation.FlaggedApi; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.VirtualDeviceManager; +import android.companion.virtual.VirtualDeviceParams; +import android.companion.virtual.flags.Flags; +import android.hardware.camera2.CameraDevice; import android.os.RemoteException; import androidx.annotation.NonNull; +import java.io.Closeable; import java.util.Objects; +import java.util.concurrent.Executor; /** - * Virtual camera that is used to send image data into system. + * A VirtualCamera is the representation of a remote or computer generated camera that will be + * exposed to applications using the Android Camera APIs. * + * <p>A VirtualCamera is created using {@link + * VirtualDeviceManager.VirtualDevice#createVirtualCamera(VirtualCameraConfig)}. + * + * <p>Once a virtual camera is created, it will receive callbacks from the system when an + * application attempts to use it via the {@link VirtualCameraCallback} class set using {@link + * VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback)} + * + * @see VirtualDeviceManager.VirtualDevice#createVirtualDevice(int, VirtualDeviceParams) + * @see VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback) + * @see android.hardware.camera2.CameraManager#openCamera(String, CameraDevice.StateCallback, + * android.os.Handler) * @hide */ -public final class VirtualCamera extends IVirtualCamera.Stub { +@SystemApi +@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) +public final class VirtualCamera implements Closeable { + private final IVirtualDevice mVirtualDevice; private final VirtualCameraConfig mConfig; /** @@ -37,40 +61,35 @@ public final class VirtualCamera extends IVirtualCamera.Stub { * * @param virtualDevice The Binder object representing this camera in the server. * @param config Configuration for the new virtual camera + * @hide */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public VirtualCamera( @NonNull IVirtualDevice virtualDevice, @NonNull VirtualCameraConfig config) { + mVirtualDevice = virtualDevice; mConfig = Objects.requireNonNull(config); Objects.requireNonNull(virtualDevice); + // TODO(b/310857519): Avoid registration inside constructor. try { - virtualDevice.registerVirtualCamera(this); + mVirtualDevice.registerVirtualCamera(config); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } - /** Get the camera session associated with this device */ - @Override - public IVirtualCameraSession open() { - // TODO: b/302255544 - Make this async. - VirtualCameraSession session = mConfig.getCallback().onOpenSession(); - return new VirtualCameraSessionInternal(session); - } - /** Returns the configuration of this virtual camera instance. */ @NonNull public VirtualCameraConfig getConfig() { return mConfig; } - /** - * Returns the configuration to be used by the virtual camera HAL. - * - * @hide - */ @Override - @NonNull - public VirtualCameraHalConfig getHalConfig() { - return mConfig.getHalConfig(); + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void close() { + try { + mVirtualDevice.unregisterVirtualCamera(mConfig); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } } diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java index a7c3d4fac7dc..a18ae03555e9 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java +++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java @@ -16,7 +16,12 @@ package android.companion.virtual.camera; -import android.hardware.camera2.params.SessionConfiguration; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.view.Surface; import java.util.concurrent.Executor; @@ -24,15 +29,53 @@ import java.util.concurrent.Executor; * Interface to be provided when creating a new {@link VirtualCamera} in order to receive callbacks * from the framework and the camera system. * - * @see VirtualCameraConfig.Builder#setCallback(Executor, VirtualCameraCallback) + * @see VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback) * @hide */ +@SystemApi +@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) public interface VirtualCameraCallback { /** - * Called when a client opens a new camera session for the associated {@link VirtualCamera} + * Called when one of the requested stream has been configured by the virtual camera service and + * is ready to receive data onto its {@link Surface} * - * @see android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration) + * @param streamId The id of the configured stream + * @param surface The surface to write data into for this stream + * @param streamConfig The image data configuration for this stream */ - VirtualCameraSession onOpenSession(); + void onStreamConfigured( + int streamId, + @NonNull Surface surface, + @NonNull VirtualCameraStreamConfig streamConfig); + + /** + * The client application is requesting a camera frame for the given streamId with the provided + * metadata. + * + * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to + * this stream that was provided during the {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} call. + * + * @param streamId The streamId for which the frame is requested. This corresponds to the + * streamId that was given in {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} + * @param frameId The frameId that is being requested. Each request will have a different + * frameId, that will be increasing for each call with a particular streamId. + * @param metadata The metadata requested for the frame. The virtual camera should do its best + * to honor the requested metadata but the consumer won't be informed about the metadata set + * for a particular frame. If null, the requested frame can be anything the producer sends. + */ + void onProcessCaptureRequest( + int streamId, long frameId, @Nullable VirtualCameraMetadata metadata); + + /** + * The stream previously configured when {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be + * freed. The Surface corresponding to that streamId was disposed on the client side and should + * not be used anymore by the virtual camera owner + * + * @param streamId The id of the stream that was closed. + */ + void onStreamClosed(int streamId); } diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSession.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl index c25d97711e75..88c27a52bb9d 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraSession.java +++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl @@ -13,18 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package android.companion.virtual.camera; -/*** - * Counterpart of {@link android.hardware.camera2.CameraCaptureSession} for producing - * images from a {@link VirtualCamera}. - * @hide - */ -// TODO: b/289881985 - This is just a POC implementation for now, this will be extended -// to a full featured Camera Session -public interface VirtualCameraSession { - - /** Close the session and release its resources. */ - default void close() {} -} +/** @hide */ +parcelable VirtualCameraConfig;
\ No newline at end of file diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java index fb464d53b9b5..f1eb240301e0 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java +++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java @@ -18,11 +18,19 @@ package android.companion.virtual.camera; import static java.util.Objects.requireNonNull; +import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.StringRes; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.content.res.Resources; import android.graphics.ImageFormat; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArraySet; +import android.view.Surface; -import java.util.Collections; import java.util.Set; import java.util.concurrent.Executor; @@ -33,33 +41,115 @@ import java.util.concurrent.Executor; * * @hide */ -public final class VirtualCameraConfig { +@SystemApi +@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) +public final class VirtualCameraConfig implements Parcelable { - private final String mDisplayName; + private final @StringRes int mNameStringRes; private final Set<VirtualCameraStreamConfig> mStreamConfigurations; - private final VirtualCameraCallback mCallback; - private final Executor mCallbackExecutor; + private final IVirtualCameraCallback mCallback; + + private VirtualCameraConfig( + int displayNameStringRes, + @NonNull Set<VirtualCameraStreamConfig> streamConfigurations, + @NonNull Executor executor, + @NonNull VirtualCameraCallback callback) { + mNameStringRes = displayNameStringRes; + mStreamConfigurations = + Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations")); + if (mStreamConfigurations.isEmpty()) { + throw new IllegalArgumentException( + "At least one stream configuration is needed to create a virtual camera."); + } + mCallback = + new VirtualCameraCallbackInternal( + requireNonNull(callback, "Missing callback"), + requireNonNull(executor, "Missing callback executor")); + } + + private VirtualCameraConfig(@NonNull Parcel in) { + mNameStringRes = in.readInt(); + mCallback = IVirtualCameraCallback.Stub.asInterface(in.readStrongBinder()); + mStreamConfigurations = + Set.of( + in.readParcelableArray( + VirtualCameraStreamConfig.class.getClassLoader(), + VirtualCameraStreamConfig.class)); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mNameStringRes); + dest.writeStrongInterface(mCallback); + dest.writeParcelableArray( + mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags); + } + + /** + * @return The display name of this VirtualCamera + */ + @StringRes + public int getDisplayNameStringRes() { + return mNameStringRes; + } + + /** + * Returns an unmodifiable set of the stream configurations added to this {@link + * VirtualCameraConfig}. + * + * @see VirtualCameraConfig.Builder#addStreamConfig(int, int, int) + */ + @NonNull + public Set<VirtualCameraStreamConfig> getStreamConfigs() { + return mStreamConfigurations; + } + + /** + * Returns the callback used to communicate from the server to the client. + * + * @hide + */ + @NonNull + public IVirtualCameraCallback getCallback() { + return mCallback; + } /** * Builder for {@link VirtualCameraConfig}. * * <p>To build an instance of {@link VirtualCameraConfig} the following conditions must be met: - * <li>At least one stream must be added wit {@link #addStreamConfiguration(int, int, int)}. - * <li>A name must be set with {@link #setDisplayName(String)} - * <li>A callback must be set wit {@link #setCallback(Executor, VirtualCameraCallback)} + * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}. + * <li>A callback must be set with {@link #setVirtualCameraCallback(Executor, + * VirtualCameraCallback)} + * <li>A user readable name can be set with {@link #setDisplayNameStringRes(int)} */ + @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) public static final class Builder { - private String mDisplayName; - private final ArraySet<VirtualCameraStreamConfig> mStreamConfiguration = new ArraySet<>(); + private @StringRes int mDisplayNameStringRes = Resources.ID_NULL; + private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>(); private Executor mCallbackExecutor; private VirtualCameraCallback mCallback; - /** Set the visible name of this camera for the user. */ - // TODO: b/290172356 - Take a resource id instead of displayName + /** + * Set the visible name of this camera for the user. + * + * <p>Sets the resource to a string representing a user readable name for this virtual + * camera. + * + * @throws IllegalArgumentException if an invalid resource id is passed. + */ @NonNull - public Builder setDisplayName(@NonNull String displayName) { - mDisplayName = requireNonNull(displayName); + public Builder setDisplayNameStringRes(@StringRes int displayNameStringRes) { + if (displayNameStringRes <= 0) { + throw new IllegalArgumentException("Invalid resource passed for display name"); + } + mDisplayNameStringRes = displayNameStringRes; return this; } @@ -68,18 +158,21 @@ public final class VirtualCameraConfig { * * <p>At least one {@link VirtualCameraStreamConfig} must be added. * - * @param width The width of the stream - * @param height The height of the stream - * @param format The {@link ImageFormat} of the stream + * @param width The width of the stream. + * @param height The height of the stream. + * @param format The {@link ImageFormat} of the stream. + * + * @throws IllegalArgumentException if invalid format or dimensions are passed. */ @NonNull - public Builder addStreamConfiguration( - int width, int height, @ImageFormat.Format int format) { - VirtualCameraStreamConfig streamConfig = new VirtualCameraStreamConfig(); - streamConfig.width = width; - streamConfig.height = height; - streamConfig.format = format; - mStreamConfiguration.add(streamConfig); + public Builder addStreamConfig(int width, int height, @ImageFormat.Format int format) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Invalid dimensions passed for stream config"); + } + if (!ImageFormat.isPublicFormat(format)) { + throw new IllegalArgumentException("Invalid format passed for stream config"); + } + mStreamConfigurations.add(new VirtualCameraStreamConfig(width, height, format)); return this; } @@ -93,7 +186,9 @@ public final class VirtualCameraConfig { * @param callback The instance of the callback to be added. Subsequent call to this method * will replace the callback set. */ - public Builder setCallback( + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") // The configuration is immutable + public Builder setVirtualCameraCallback( @NonNull Executor executor, @NonNull VirtualCameraCallback callback) { mCallbackExecutor = requireNonNull(executor); mCallback = requireNonNull(callback); @@ -108,67 +203,49 @@ public final class VirtualCameraConfig { @NonNull public VirtualCameraConfig build() { return new VirtualCameraConfig( - mDisplayName, mStreamConfiguration, mCallbackExecutor, mCallback); + mDisplayNameStringRes, mStreamConfigurations, mCallbackExecutor, mCallback); } } - private VirtualCameraConfig( - @NonNull String displayName, - @NonNull Set<VirtualCameraStreamConfig> streamConfigurations, - @NonNull Executor executor, - @NonNull VirtualCameraCallback callback) { - mDisplayName = requireNonNull(displayName, "Missing display name"); - mStreamConfigurations = - Collections.unmodifiableSet( - requireNonNull(streamConfigurations, "Missing stream configuration")); - if (mStreamConfigurations.isEmpty()) { - throw new IllegalArgumentException( - "At least one StreamConfiguration is needed to create a virtual camera."); - } - mCallback = requireNonNull(callback, "Missing callback"); - mCallbackExecutor = requireNonNull(executor, "Missing callback executor"); - } + private static class VirtualCameraCallbackInternal extends IVirtualCameraCallback.Stub { - /** - * @return The display name of this VirtualCamera - */ - @NonNull - public String getDisplayName() { - return mDisplayName; - } + private final VirtualCameraCallback mCallback; + private final Executor mExecutor; - /** - * Returns an unmodifiable set of the stream configurations added to this {@link - * VirtualCameraConfig}. - * - * @see VirtualCameraConfig.Builder#addStreamConfiguration(int, int, int) - */ - @NonNull - public Set<VirtualCameraStreamConfig> getStreamConfigs() { - return mStreamConfigurations; - } + private VirtualCameraCallbackInternal(VirtualCameraCallback callback, Executor executor) { + mCallback = callback; + mExecutor = executor; + } - /** Returns the callback used to communicate from the server to the client. */ - @NonNull - public VirtualCameraCallback getCallback() { - return mCallback; - } + @Override + public void onStreamConfigured( + int streamId, Surface surface, VirtualCameraStreamConfig streamConfig) { + mExecutor.execute(() -> mCallback.onStreamConfigured(streamId, surface, streamConfig)); + } - /** Returns the executor onto which the callback should be run. */ - @NonNull - public Executor getCallbackExecutor() { - return mCallbackExecutor; + @Override + public void onProcessCaptureRequest( + int streamId, long frameId, VirtualCameraMetadata metadata) { + mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId, metadata)); + } + + @Override + public void onStreamClosed(int streamId) { + mExecutor.execute(() -> mCallback.onStreamClosed(streamId)); + } } - /** - * Returns a new instance of {@link VirtualCameraHalConfig} initialized with data from this - * {@link VirtualCameraConfig} - */ @NonNull - public VirtualCameraHalConfig getHalConfig() { - VirtualCameraHalConfig halConfig = new VirtualCameraHalConfig(); - halConfig.displayName = mDisplayName; - halConfig.streamConfigs = mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]); - return halConfig; - } + public static final Parcelable.Creator<VirtualCameraConfig> CREATOR = + new Parcelable.Creator<>() { + @Override + public VirtualCameraConfig createFromParcel(Parcel in) { + return new VirtualCameraConfig(in); + } + + @Override + public VirtualCameraConfig[] newArray(int size) { + return new VirtualCameraConfig[size]; + } + }; } diff --git a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl deleted file mode 100644 index 7070a38b6414..000000000000 --- a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl +++ /dev/null @@ -1,12 +0,0 @@ -package android.companion.virtual.camera; - -import android.companion.virtual.camera.VirtualCameraStreamConfig; - -/** - * Configuration for VirtualCamera to be passed to the server and HAL service. - * @hide - */ -parcelable VirtualCameraHalConfig { - String displayName; - VirtualCameraStreamConfig[] streamConfigs; -} diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl index 252980773264..6c1f0fcd622a 100644 --- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl +++ b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl @@ -13,16 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.companion.virtual.camera; /** - * Counterpart of ICameraDeviceSession for virtual camera. - * + * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with + * VirtualCamera. * @hide */ -interface IVirtualCameraSession { - - void configureStream(int width, int height, int format); - - void close(); -} +parcelable VirtualCameraMetadata; diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java new file mode 100644 index 000000000000..1ba36d08cbeb --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.camera; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Data structure used to store camera metadata compatible with VirtualCamera. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) +public final class VirtualCameraMetadata implements Parcelable { + + /** @hide */ + public VirtualCameraMetadata(@NonNull Parcel in) {} + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) {} + + @NonNull + public static final Creator<VirtualCameraMetadata> CREATOR = + new Creator<>() { + @Override + @NonNull + public VirtualCameraMetadata createFromParcel(Parcel in) { + return new VirtualCameraMetadata(in); + } + + @Override + @NonNull + public VirtualCameraMetadata[] newArray(int size) { + return new VirtualCameraMetadata[size]; + } + }; +} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java b/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java deleted file mode 100644 index da168de41cee..000000000000 --- a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.companion.virtual.camera; - -import android.graphics.ImageFormat; - -import androidx.annotation.NonNull; - -import java.util.Objects; - -/** - * Wraps the client side {@link VirtualCameraSession} into an {@link IVirtualCameraSession}. - * - * @hide - */ -final class VirtualCameraSessionInternal extends IVirtualCameraSession.Stub { - - @SuppressWarnings("FieldCanBeLocal") - // TODO: b/289881985: Will be used once connected with the CameraService - private final VirtualCameraSession mVirtualCameraSession; - - VirtualCameraSessionInternal(@NonNull VirtualCameraSession virtualCameraSession) { - mVirtualCameraSession = Objects.requireNonNull(virtualCameraSession); - } - - @Override - public void configureStream(int width, int height, @ImageFormat.Format int format) {} - - public void close() {} -} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl index 304d4553bd2a..ce92b6d48f0d 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl +++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl @@ -16,11 +16,7 @@ package android.companion.virtual.camera; /** - * A stream configuration supported by a virtual camera + * The configuration of a single virtual camera stream. * @hide */ -parcelable VirtualCameraStreamConfig { - int width; - int height; - int format; -} +parcelable VirtualCameraStreamConfig;
\ No newline at end of file diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java new file mode 100644 index 000000000000..e198821e860e --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.companion.virtual.camera; + +import android.annotation.FlaggedApi; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.graphics.ImageFormat; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * The configuration of a single virtual camera stream. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) +public final class VirtualCameraStreamConfig implements Parcelable { + + private final int mWidth; + private final int mHeight; + private final int mFormat; + + /** + * Construct a new instance of {@link VirtualCameraStreamConfig} initialized with the provided + * width, height and {@link ImageFormat} + * + * @param width The width of the stream. + * @param height The height of the stream. + * @param format The {@link ImageFormat} of the stream. + */ + public VirtualCameraStreamConfig( + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @ImageFormat.Format int format) { + this.mWidth = width; + this.mHeight = height; + this.mFormat = format; + } + + private VirtualCameraStreamConfig(@NonNull Parcel in) { + mWidth = in.readInt(); + mHeight = in.readInt(); + mFormat = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mWidth); + dest.writeInt(mHeight); + dest.writeInt(mFormat); + } + + @NonNull + public static final Creator<VirtualCameraStreamConfig> CREATOR = + new Creator<>() { + @Override + public VirtualCameraStreamConfig createFromParcel(Parcel in) { + return new VirtualCameraStreamConfig(in); + } + + @Override + public VirtualCameraStreamConfig[] newArray(int size) { + return new VirtualCameraStreamConfig[size]; + } + }; + + /** Returns the width of this stream. */ + @IntRange(from = 1) + public int getWidth() { + return mWidth; + } + + /** Returns the height of this stream. */ + @IntRange(from = 1) + public int getHeight() { + return mHeight; + } + + /** Returns the {@link ImageFormat} of this stream. */ + @ImageFormat.Format + public int getFormat() { + return mFormat; + } +} diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index cfab9ebec593..02066fa8a34e 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -58,6 +58,13 @@ flag { } flag { + name: "persistent_device_id_api" + namespace: "virtual_devices" + description: "Enable persistent device ID notification API" + bug: "295258915" +} + +flag { name: "express_metrics" namespace: "virtual_devices" description: "Enable express metrics in VDM" diff --git a/core/java/android/content/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/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index aca6d0646a9b..5d0697806512 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -3402,8 +3402,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> new Key<Long>("android.sensor.exposureTime", long.class); /** - * <p>Duration from start of frame exposure to - * start of next frame exposure.</p> + * <p>Duration from start of frame readout to + * start of next frame readout.</p> * <p>The maximum frame rate that can be supported by a camera subsystem is * a function of many factors:</p> * <ul> @@ -3464,6 +3464,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> + * <p><em>Note:</em> Prior to Android 13, this field was described as measuring the duration from + * start of frame exposure to start of next frame exposure, which doesn't reflect the + * definition from sensor manufacturer. A mobile sensor defines the frame duration as + * intervals between sensor readouts.</p> * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }. diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 1c66f82767e6..0d204f3ececa 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -4103,8 +4103,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Long>("android.sensor.exposureTime", long.class); /** - * <p>Duration from start of frame exposure to - * start of next frame exposure.</p> + * <p>Duration from start of frame readout to + * start of next frame readout.</p> * <p>The maximum frame rate that can be supported by a camera subsystem is * a function of many factors:</p> * <ul> @@ -4165,6 +4165,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> + * <p><em>Note:</em> Prior to Android 13, this field was described as measuring the duration from + * start of frame exposure to start of next frame exposure, which doesn't reflect the + * definition from sensor manufacturer. A mobile sensor defines the frame duration as + * intervals between sensor readouts.</p> * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }. diff --git a/core/java/android/hardware/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/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/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 0c95c2ec7a7a..f6beec179d57 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -84,4 +84,6 @@ interface INfcAdapter boolean isReaderOptionSupported(); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") boolean enableReaderOption(boolean enable); + boolean isObserveModeSupported(); + boolean setObserveMode(boolean enabled); } diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl index c7b3b2c03f65..191385a3c13d 100644 --- a/core/java/android/nfc/INfcCardEmulation.aidl +++ b/core/java/android/nfc/INfcCardEmulation.aidl @@ -30,6 +30,7 @@ interface INfcCardEmulation boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid); boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category); boolean setDefaultForNextTap(int userHandle, in ComponentName service); + boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable); boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup); boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement); boolean unsetOffHostForService(int userHandle, in ComponentName service); diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index c89759553810..98a980f5e7f8 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -1081,6 +1081,61 @@ public final class NfcAdapter { } } + + /** + * Returns whether the device supports observer mode or not. When observe + * mode is enabled, the NFC hardware will listen for NFC readers, but not + * respond to them. When observe mode is disabled, the NFC hardware will + * resoond to the reader and proceed with the transaction. + * @return true if the mode is supported, false otherwise. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean isObserveModeSupported() { + try { + return sService.isObserveModeSupported(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** + * Disables observe mode to allow the transaction to proceed. See + * {@link #isObserveModeSupported()} for a description of observe mode and + * use {@link #disallowTransaction()} to enable observe mode and block + * transactions again. + * + * @return boolean indicating success or failure. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean allowTransaction() { + try { + return sService.setObserveMode(false); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** + * Signals that the transaction has completed and observe mode may be + * reenabled. See {@link #isObserveModeSupported()} for a description of + * observe mode and use {@link #allowTransaction()} to disable observe + * mode and allow transactions to proceed. + * + * @return boolean indicating success or failure. + */ + + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean disallowTransaction() { + try { + return sService.setObserveMode(true); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + /** * Resumes default polling for the current device state if polling is paused. Calling * this while polling is not paused is a no-op. diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index d048b595ad1e..58b6179691e9 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -328,6 +328,24 @@ public final class CardEmulation { return SELECTION_MODE_ASK_IF_CONFLICT; } } + /** + * Sets whether the system should default to observe mode or not when + * the service is in the foreground or the default payment service. + * + * @param service The component name of the service + * @param enable Whether the servic should default to observe mode or not + * @return whether the change was successful. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean setServiceObserveModeDefault(@NonNull ComponentName service, boolean enable) { + try { + return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(), + service, enable); + } catch (RemoteException e) { + Log.e(TAG, "Failed to reach CardEmulationService."); + } + return false; + } /** * Registers a list of AIDs for a specific category for the diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java index 55d0e73780a2..7cd2533a7dbf 100644 --- a/core/java/android/nfc/cardemulation/HostApduService.java +++ b/core/java/android/nfc/cardemulation/HostApduService.java @@ -16,11 +16,14 @@ package android.nfc.cardemulation; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; +import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -29,6 +32,9 @@ import android.os.Messenger; import android.os.RemoteException; import android.util.Log; +import java.util.ArrayList; +import java.util.List; + /** * <p>HostApduService is a convenience {@link Service} class that can be * extended to emulate an NFC card inside an Android @@ -230,9 +236,99 @@ public abstract class HostApduService extends Service { /** * @hide */ + public static final int MSG_POLLING_LOOP = 4; + + /** + * @hide + */ public static final String KEY_DATA = "data"; /** + * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of + * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE"; + + /** + * POLLING_LOOP_TYPE_A is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-A. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_A = 'A'; + + /** + * POLLING_LOOP_TYPE_B is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-B. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_B = 'B'; + + /** + * POLLING_LOOP_TYPE_F is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-F. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_F = 'F'; + + /** + * POLLING_LOOP_TYPE_ON is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop turns on. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_ON = 'O'; + + /** + * POLLING_LOOP_TYPE_OFF is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop turns off. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_OFF = 'X'; + + /** + * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop frame isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U'; + + /** + * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA"; + + /** + * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN"; + + /** + * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP"; + + /** + * @hide + */ + public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY = + "android.nfc.cardemulation.POLLING_FRAMES"; + + /** * Messenger interface to NfcService for sending responses. * Only accessed on main thread by the message handler. * @@ -255,6 +351,7 @@ public abstract class HostApduService extends Service { byte[] apdu = dataBundle.getByteArray(KEY_DATA); if (apdu != null) { + HostApduService has = HostApduService.this; byte[] responseApdu = processCommandApdu(apdu, null); if (responseApdu != null) { if (mNfcService == null) { @@ -306,6 +403,12 @@ public abstract class HostApduService extends Service { Log.e(TAG, "RemoteException calling into NfcService."); } break; + case MSG_POLLING_LOOP: + ArrayList<Bundle> frames = + msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY, + Bundle.class); + processPollingFrames(frames); + break; default: super.handleMessage(msg); } @@ -366,6 +469,21 @@ public abstract class HostApduService extends Service { } } + /** + * This method is called when a polling frame has been received from a + * remote device. If the device is in observe mode, the service should + * call {@link NfcAdapter#allowTransaction()} once it is ready to proceed + * with the transaction. If the device is not in observe mode, the service + * can use this polling frame information to determine how to proceed if it + * subsequently has {@link #processCommandApdu(byte[], Bundle)} called. The + * service must override this method inorder to receive polling frames, + * otherwise the base implementation drops the frame. + * + * @param frame A description of the polling frame. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public void processPollingFrames(@NonNull List<Bundle> frame) { + } /** * <p>This method will be called when a command APDU has been received diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig index cd50ace036de..17e042761dbe 100644 --- a/core/java/android/nfc/flags.aconfig +++ b/core/java/android/nfc/flags.aconfig @@ -20,3 +20,31 @@ flag { description: "Flag for NFC user restriction" bug: "291187960" } + +flag { + name: "nfc_observe_mode" + namespace: "nfc" + description: "Enable NFC Observe Mode" + bug: "294217286" +} + +flag { + name: "nfc_read_polling_loop" + namespace: "nfc" + description: "Enable NFC Polling Loop Notifications" + bug: "294217286" +} + +flag { + name: "nfc_observe_mode_st_shim" + namespace: "nfc" + description: "Enable NFC Observe Mode ST shim" + bug: "294217286" +} + +flag { + name: "nfc_read_polling_loop_st_shim" + namespace: "nfc" + description: "Enable NFC Polling Loop Notifications ST shim" + bug: "294217286" +} diff --git a/core/java/android/os/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/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/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 2d1802ae85e5..6853892348d9 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2133,6 +2133,7 @@ public class StorageManager { MOUNT_MODE_EXTERNAL_PASS_THROUGH, MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE }) + @Retention(RetentionPolicy.SOURCE) /** @hide */ public @interface MountMode {} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 7369740bc43c..fb2ac7112b0a 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -44,6 +44,13 @@ flag { } flag { + name: "enhanced_confirmation_mode_apis" + namespace: "permissions" + description: "enable enhanced confirmation mode apis" + bug: "310220212" +} + +flag { name: "op_enable_mobile_data_by_user" namespace: "permissions" description: "enables logging of the OP_ENABLE_MOBILE_DATA_BY_USER" diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c012ff34bfab..4e7734c5471d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -19039,6 +19039,14 @@ public final class Settings { public static final int BATTERY_SAVER_MODE_CUSTOM = 4; /** + Whether 1P apps vote for enabling data during different modes, + i.e. BTM, BBSM + * @hide + */ + @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final String CONNECTIVITY_KEEP_DATA_ON = "wear_connectivity_keep_data_on"; + + /** * The maximum ambient mode duration when an activity is allowed to auto resume. * @hide */ diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index bcda25a1bf3b..72c436ee6d41 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -17,6 +17,7 @@ package android.provider; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -49,6 +50,7 @@ import android.text.TextUtils; import android.util.Patterns; import com.android.internal.telephony.SmsApplication; +import com.android.internal.telephony.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -3191,8 +3193,8 @@ public final class Telephony { * Sets whether the PDU session brought up by this APN should always be on. * See 3GPP TS 23.501 section 5.6.13 * <P>Type: INTEGER</P> - * @hide */ + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String ALWAYS_ON = "always_on"; /** @@ -3302,18 +3304,16 @@ public final class Telephony { * The MTU (maximum transmit unit) size of the mobile interface for IPv4 to which the APN is * connected, in bytes. * <p>Type: INTEGER </p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String MTU_V4 = "mtu_v4"; /** * The MTU (maximum transmit unit) size of the mobile interface for IPv6 to which the APN is * connected, in bytes. * <p>Type: INTEGER </p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String MTU_V6 = "mtu_v6"; /** @@ -3335,17 +3335,15 @@ public final class Telephony { /** * {@code true} if this APN visible to the user, {@code false} otherwise. * <p>Type: INTEGER (boolean)</p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String USER_VISIBLE = "user_visible"; /** * {@code true} if the user allowed to edit this APN, {@code false} otherwise. * <p>Type: INTEGER (boolean)</p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String USER_EDITABLE = "user_editable"; /** diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 759953e4aca4..92c516c38dcc 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -309,6 +309,7 @@ public abstract class NotificationListenerService extends Service { REASON_ASSISTANT_CANCEL, REASON_LOCKDOWN, }) + @Retention(RetentionPolicy.SOURCE) public @interface NotificationCancelReason{}; /** @@ -320,6 +321,7 @@ public abstract class NotificationListenerService extends Service { FLAG_FILTER_TYPE_SILENT, FLAG_FILTER_TYPE_ONGOING }) + @Retention(RetentionPolicy.SOURCE) public @interface NotificationFilterTypes {} /** * A flag value indicating that this notification listener can see conversation type diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java new file mode 100644 index 000000000000..5b096c641f78 --- /dev/null +++ b/core/java/android/service/notification/ZenDeviceEffects.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.app.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * Represents the set of device effects (affecting display and device behavior in general) that + * are applied whenever an {@link android.app.AutomaticZenRule} is active. + */ +@FlaggedApi(Flags.FLAG_MODES_API) +public final class ZenDeviceEffects implements Parcelable { + + private final boolean mGrayscale; + private final boolean mSuppressAmbientDisplay; + private final boolean mDimWallpaper; + private final boolean mNightMode; + + private final boolean mDisableAutoBrightness; + private final boolean mDisableTapToWake; + private final boolean mDisableTiltToWake; + private final boolean mDisableTouch; + private final boolean mMinimizeRadioUsage; + private final boolean mMaximizeDoze; + + private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay, + boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness, + boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch, + boolean minimizeRadioUsage, boolean maximizeDoze) { + mGrayscale = grayscale; + mSuppressAmbientDisplay = suppressAmbientDisplay; + mDimWallpaper = dimWallpaper; + mNightMode = nightMode; + mDisableAutoBrightness = disableAutoBrightness; + mDisableTapToWake = disableTapToWake; + mDisableTiltToWake = disableTiltToWake; + mDisableTouch = disableTouch; + mMinimizeRadioUsage = minimizeRadioUsage; + mMaximizeDoze = maximizeDoze; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof final ZenDeviceEffects that)) return false; + if (obj == this) return true; + + return this.mGrayscale == that.mGrayscale + && this.mSuppressAmbientDisplay == that.mSuppressAmbientDisplay + && this.mDimWallpaper == that.mDimWallpaper + && this.mNightMode == that.mNightMode + && this.mDisableAutoBrightness == that.mDisableAutoBrightness + && this.mDisableTapToWake == that.mDisableTapToWake + && this.mDisableTiltToWake == that.mDisableTiltToWake + && this.mDisableTouch == that.mDisableTouch + && this.mMinimizeRadioUsage == that.mMinimizeRadioUsage + && this.mMaximizeDoze == that.mMaximizeDoze; + } + + @Override + public int hashCode() { + return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode, + mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch, + mMinimizeRadioUsage, mMaximizeDoze); + } + + @Override + public String toString() { + ArrayList<String> effects = new ArrayList<>(10); + if (mGrayscale) effects.add("grayscale"); + if (mSuppressAmbientDisplay) effects.add("suppressAmbientDisplay"); + if (mDimWallpaper) effects.add("dimWallpaper"); + if (mNightMode) effects.add("nightMode"); + if (mDisableAutoBrightness) effects.add("disableAutoBrightness"); + if (mDisableTapToWake) effects.add("disableTapToWake"); + if (mDisableTiltToWake) effects.add("disableTiltToWake"); + if (mDisableTouch) effects.add("disableTouch"); + if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage"); + if (mMaximizeDoze) effects.add("maximizeDoze"); + return "[" + String.join(", ", effects) + "]"; + } + + /** + * Whether the level of color saturation of the display should be set to minimum, effectively + * switching it to grayscale, while the rule is active. + */ + public boolean shouldDisplayGrayscale() { + return mGrayscale; + } + + /** + * Whether the ambient (always-on) display feature should be disabled while the rule is active. + * This will have no effect if the device doesn't support always-on display or if it's not + * generally enabled. + */ + public boolean shouldSuppressAmbientDisplay() { + return mSuppressAmbientDisplay; + } + + /** Whether the wallpaper should be dimmed while the rule is active. */ + public boolean shouldDimWallpaper() { + return mDimWallpaper; + } + + /** Whether night mode (aka dark theme) should be applied while the rule is active. */ + public boolean shouldUseNightMode() { + return mNightMode; + } + + /** + * Whether the display's automatic brightness adjustment should be disabled while the rule is + * active. + * @hide + */ + public boolean shouldDisableAutoBrightness() { + return mDisableAutoBrightness; + } + + /** + * Whether "tap to wake" should be disabled while the rule is active. + * @hide + */ + public boolean shouldDisableTapToWake() { + return mDisableTapToWake; + } + + /** + * Whether "tilt to wake" should be disabled while the rule is active. + * @hide + */ + public boolean shouldDisableTiltToWake() { + return mDisableTiltToWake; + } + + /** + * Whether touch interactions should be disabled while the rule is active. + * @hide + */ + public boolean shouldDisableTouch() { + return mDisableTouch; + } + + /** + * Whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption, should be + * minimized while the rule is active. + * @hide + */ + public boolean shouldMinimizeRadioUsage() { + return mMinimizeRadioUsage; + } + + /** + * Whether Doze should be enhanced (e.g. with more aggresive activation, or less frequent + * maintenance windows) while the rule is active. + * @hide + */ + public boolean shouldMaximizeDoze() { + return mMaximizeDoze; + } + + /** {@link Parcelable.Creator} that instantiates {@link ZenDeviceEffects} objects. */ + @NonNull + public static final Creator<ZenDeviceEffects> CREATOR = new Creator<ZenDeviceEffects>() { + @Override + public ZenDeviceEffects createFromParcel(Parcel in) { + return new ZenDeviceEffects(in.readBoolean(), in.readBoolean(), in.readBoolean(), + in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), + in.readBoolean(), in.readBoolean(), in.readBoolean()); + } + + @Override + public ZenDeviceEffects[] newArray(int size) { + return new ZenDeviceEffects[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBoolean(mGrayscale); + dest.writeBoolean(mSuppressAmbientDisplay); + dest.writeBoolean(mDimWallpaper); + dest.writeBoolean(mNightMode); + dest.writeBoolean(mDisableAutoBrightness); + dest.writeBoolean(mDisableTapToWake); + dest.writeBoolean(mDisableTiltToWake); + dest.writeBoolean(mDisableTouch); + dest.writeBoolean(mMinimizeRadioUsage); + dest.writeBoolean(mMaximizeDoze); + } + + /** Builder class for {@link ZenDeviceEffects} objects. */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final class Builder { + + private boolean mGrayscale; + private boolean mSuppressAmbientDisplay; + private boolean mDimWallpaper; + private boolean mNightMode; + private boolean mDisableAutoBrightness; + private boolean mDisableTapToWake; + private boolean mDisableTiltToWake; + private boolean mDisableTouch; + private boolean mMinimizeRadioUsage; + private boolean mMaximizeDoze; + + /** + * Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled). + */ + public Builder() { + } + + /** + * Instantiates a new {@link ZenPolicy.Builder} with all effects set to their corresponding + * values in the supplied {@link ZenDeviceEffects}. + */ + public Builder(@NonNull ZenDeviceEffects zenDeviceEffects) { + mGrayscale = zenDeviceEffects.shouldDisplayGrayscale(); + mSuppressAmbientDisplay = zenDeviceEffects.shouldSuppressAmbientDisplay(); + mDimWallpaper = zenDeviceEffects.shouldDimWallpaper(); + mNightMode = zenDeviceEffects.shouldUseNightMode(); + mDisableAutoBrightness = zenDeviceEffects.shouldDisableAutoBrightness(); + mDisableTapToWake = zenDeviceEffects.shouldDisableTapToWake(); + mDisableTiltToWake = zenDeviceEffects.shouldDisableTiltToWake(); + mDisableTouch = zenDeviceEffects.shouldDisableTouch(); + mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage(); + mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze(); + } + + /** + * Sets whether the level of color saturation of the display should be set to minimum, + * effectively switching it to grayscale, while the rule is active. + */ + @NonNull + public Builder setShouldDisplayGrayscale(boolean grayscale) { + mGrayscale = grayscale; + return this; + } + + /** + * Sets whether the ambient (always-on) display feature should be disabled while the rule + * is active. This will have no effect if the device doesn't support always-on display or if + * it's not generally enabled. + */ + @NonNull + public Builder setShouldSuppressAmbientDisplay(boolean suppressAmbientDisplay) { + mSuppressAmbientDisplay = suppressAmbientDisplay; + return this; + } + + /** Sets whether the wallpaper should be dimmed while the rule is active. */ + @NonNull + public Builder setShouldDimWallpaper(boolean dimWallpaper) { + mDimWallpaper = dimWallpaper; + return this; + } + + /** Sets whether night mode (aka dark theme) should be applied while the rule is active. */ + @NonNull + public Builder setShouldUseNightMode(boolean nightMode) { + mNightMode = nightMode; + return this; + } + + /** + * Sets whether the display's automatic brightness adjustment should be disabled while the + * rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableAutoBrightness(boolean disableAutoBrightness) { + mDisableAutoBrightness = disableAutoBrightness; + return this; + } + + /** + * Sets whether "tap to wake" should be disabled while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableTapToWake(boolean disableTapToWake) { + mDisableTapToWake = disableTapToWake; + return this; + } + + /** + * Sets whether "tilt to wake" should be disabled while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableTiltToWake(boolean disableTiltToWake) { + mDisableTiltToWake = disableTiltToWake; + return this; + } + + /** + * Sets whether touch interactions should be disabled while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableTouch(boolean disableTouch) { + mDisableTouch = disableTouch; + return this; + } + + /** + * Sets whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption, + * should be minimized while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldMinimizeRadioUsage(boolean minimizeRadioUsage) { + mMinimizeRadioUsage = minimizeRadioUsage; + return this; + } + + /** + * Sets whether Doze should be enhanced (e.g. with more aggresive activation, or less + * frequent maintenance windows) while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldMaximizeDoze(boolean maximizeDoze) { + mMaximizeDoze = maximizeDoze; + return this; + } + + /** Builds a {@link ZenDeviceEffects} object based on the builder's state. */ + @NonNull + public ZenDeviceEffects build() { + return new ZenDeviceEffects(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, + mNightMode, mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, + mDisableTouch, mMinimizeRadioUsage, mMaximizeDoze); + } + } +} diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java index ff6dffdaaf5d..1e08fd8061e0 100644 --- a/core/java/android/service/voice/HotwordDetectedResult.java +++ b/core/java/android/service/voice/HotwordDetectedResult.java @@ -85,6 +85,7 @@ public final class HotwordDetectedResult implements Parcelable { CONFIDENCE_LEVEL_HIGH, CONFIDENCE_LEVEL_VERY_HIGH }) + @Retention(RetentionPolicy.SOURCE) @interface HotwordConfidenceLevelValue { } diff --git a/core/java/android/service/voice/HotwordRejectedResult.java b/core/java/android/service/voice/HotwordRejectedResult.java index 7b3f47d04e48..26c1ca428c3b 100644 --- a/core/java/android/service/voice/HotwordRejectedResult.java +++ b/core/java/android/service/voice/HotwordRejectedResult.java @@ -22,6 +22,9 @@ import android.os.Parcelable; import com.android.internal.util.DataClass; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Represents a result supporting the rejected hotword trigger. * @@ -57,6 +60,7 @@ public final class HotwordRejectedResult implements Parcelable { CONFIDENCE_LEVEL_MEDIUM, CONFIDENCE_LEVEL_HIGH }) + @Retention(RetentionPolicy.SOURCE) @interface HotwordConfidenceLevelValue { } diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 35834fd3886f..f1c1420de9c7 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)} @@ -886,17 +908,32 @@ public class SpeechRecognizer { /** Destroys the {@code SpeechRecognizer} object. */ public void destroy() { - if (mService != null) { - try { - mService.cancel(mListener, /*isShutdown*/ true); - } catch (final Exception e) { - // Not important + try { + if (mService != null) { + try { + mService.cancel(mListener, /*isShutdown*/ true); + } catch (final Exception e) { + // Not important + } } + + mService = null; + mPendingTasks.clear(); + mListener.mInternalListener = null; + mCloseGuard.close(); + } finally { + Reference.reachabilityFence(this); } + } - mService = null; - mPendingTasks.clear(); - mListener.mInternalListener = null; + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + destroy(); + } finally { + super.finalize(); + } } /** Establishes a connection to system server proxy and initializes the session. */ diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 766e924c994e..b91a8789a181 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -163,12 +163,6 @@ public class FeatureFlagUtils { public static final String SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS = "settings_remoteauth_enrollment"; - /** Flag to enable/disable entire page in Accessibility -> Hearing aids - * @hide - */ - public static final String SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE = - "settings_accessibility_hearing_aid_page"; - /** * Flag to enable/disable preferring the AccessibilityMenu service in the system. * @hide @@ -244,7 +238,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false"); DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false"); - DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, "true"); DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false"); DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false"); DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true"); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index c73514250e3b..d4bd9e5bab92 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 {} /** @@ -3349,6 +3351,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP, PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY, }) + @Retention(RetentionPolicy.SOURCE) public @interface PrivateFlags {} /** diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java index c4d43bcfdb24..eb2a101e8ee6 100644 --- a/core/java/android/view/inputmethod/HandwritingGesture.java +++ b/core/java/android/view/inputmethod/HandwritingGesture.java @@ -86,6 +86,7 @@ public abstract class HandwritingGesture { * Granular level on which text should be operated. */ @IntDef({GRANULARITY_CHARACTER, GRANULARITY_WORD}) + @Retention(RetentionPolicy.SOURCE) @interface Granularity {} /** diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index eeab005771f5..589b7a3eeda4 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1176,24 +1176,33 @@ public final class InputMethodManager { mActive = interactive; mFullscreenMode = fullscreen; if (interactive) { + // Find the next view focus to start the input connection when the + // device was interactive. final View rootView = mCurRootView != null ? mCurRootView.getView() : null; if (rootView == null) { + // No window focused or view was removed, ignore request. return; } - // Find the next view focus to start the input connection when the - // device was interactive. final ViewRootImpl currentViewRootImpl = mCurRootView; + // Post this on UI thread as required for view focus code. rootView.post(() -> { synchronized (mH) { if (mCurRootView != currentViewRootImpl) { + // Focused window changed since posting, ignore request. return; } } - final View focusedView = currentViewRootImpl.getView().findFocus(); + final View curRootView = currentViewRootImpl.getView(); + if (curRootView == null) { + // View was removed, ignore request. + return; + } + final View focusedView = curRootView.findFocus(); onViewFocusChangedInternal(focusedView, focusedView != null); }); } else { + // Finish input connection when device becomes non-interactive. finishInputLocked(); if (isImeSessionAvailableLocked()) { mCurBindState.mImeSession.finishInput(); diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index e44f43609256..4816f35e6a07 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -27,6 +27,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteCallback; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Information to be sent to SysUI about a back event. * @@ -85,6 +88,7 @@ public final class BackNavigationInfo implements Parcelable { TYPE_CROSS_TASK, TYPE_CALLBACK }) + @Retention(RetentionPolicy.SOURCE) public @interface BackTargetType { } diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index 0ce076b6eb96..1bd921b339f6 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -22,6 +22,8 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * The window frame container class used by client side for layout. * @hide @@ -101,6 +103,29 @@ public class ClientWindowFrames implements Parcelable { } @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ClientWindowFrames other = (ClientWindowFrames) o; + return frame.equals(other.frame) + && displayFrame.equals(other.displayFrame) + && parentFrame.equals(other.parentFrame) + && Objects.equals(attachedFrame, other.attachedFrame) + && isParentFrameClippedByDisplayCutout == other.isParentFrameClippedByDisplayCutout + && compatScale == other.compatScale; + } + + @Override + public int hashCode() { + return Objects.hash(frame, displayFrame, parentFrame, attachedFrame, + isParentFrameClippedByDisplayCutout, compatScale); + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index f1c0d8dee525..b6c04d94b476 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -33,6 +33,8 @@ import android.util.Log; import android.util.Singleton; import android.util.Slog; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; /** @@ -65,6 +67,7 @@ public interface SplashScreen { SPLASH_SCREEN_STYLE_SOLID_COLOR, SPLASH_SCREEN_STYLE_ICON }) + @Retention(RetentionPolicy.SOURCE) @interface SplashScreenStyle {} /** diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index b63a969337e0..9fe30df13036 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -6,4 +6,12 @@ flag { description: "When necessary, configuration decoupled from status bar and display cutout" bug: "291870756" is_fixed_read_only: true +} + +flag { + name: "movable_cutout_configuration" + namespace: "large_screen_experiences_app_compat" + description: "Make it possible to move cutout across edges through device config" + bug: "302387383" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 0ad6c99422b8..b600b22751ff 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -35,4 +35,11 @@ flag { name: "fullscreen_dim_flag" description: "Whether to allow showing fullscreen dim on ActivityEmbedding split" bug: "253533308" +} + +flag { + namespace: "windowing_sdk" + name: "activity_embedding_interactive_divider_flag" + description: "Whether the interactive divider feature is enabled" + bug: "293654166" }
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java index 1826f7a38e26..b0f35784fbbd 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,19 +26,6 @@ import java.util.Set; //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public interface ParsedActivity extends ParsedMainComponent { - /** - * Generate activity object that forwards user to App Details page automatically. - * This activity should be invisible to user and user should not know or see it. - * @hide - */ - @NonNull - static ParsedActivity makeAppDetailsActivity(String packageName, String processName, - int uiOptions, String taskAffinity, boolean hardwareAccelerated) { - // Proxy method since ParsedActivityImpl is supposed to be package visibility - return ParsedActivityImpl.makeAppDetailsActivity(packageName, processName, uiOptions, - taskAffinity, hardwareAccelerated); - } - int getColorMode(); int getConfigChanges(); diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemService.java index cf478b1da2e4..adb5f4f6d2a2 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttribution.java index 1a5d110ca8ff..5b623cdbaebb 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttribution.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.StringRes; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponent.java index 5b6ecbac1359..319ed7f23d47 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentation.java index c325d8dab296..76c500827a32 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentation.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfo.java index a7f7b0086c00..fee0017c4a31 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfo.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java index b926d5345149..291ed0c44ddd 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermission.java index dc5347a41fcf..813d14dc61b3 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroup.java index 64f4fbd440a2..a5ba51307f01 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroup.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; /** @hide */ //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java index 608d08eeb984..e5247f99f398 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.SuppressLint; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java b/core/java/com/android/internal/pm/pkg/component/ParsedProvider.java index c66a5c1b6c92..ba5470f12017 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java b/core/java/com/android/internal/pm/pkg/component/ParsedService.java index 5fc251ccab47..e3611021442e 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermission.java index e17d1c4f5184..984d50ddfc70 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.IntDef; import android.annotation.NonNull; diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 3496994fe173..698c5bad3801 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4303,6 +4303,9 @@ <!-- Whether the device must be screen on before routing data to this service. The default is true.--> <attr name="requireDeviceScreenOn" format="boolean"/> + <!-- Whether the device should default to observe mode when this service is + default or in the foreground. --> + <attr name="defaultToObserveMode" format="boolean"/> </declare-styleable> <!-- Use <code>offhost-apdu-service</code> as the root tag of the XML resource that @@ -4327,6 +4330,9 @@ <!-- Whether the device must be screen on before routing data to this service. The default is false.--> <attr name="requireDeviceScreenOn"/> + <!-- Whether the device should default to observe mode when this service is + default or in the foreground. --> + <attr name="defaultToObserveMode"/> </declare-styleable> <!-- Specify one or more <code>aid-group</code> elements inside a diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 764279490643..39d958cdf5ee 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -205,6 +205,13 @@ <string name="config_satellite_emergency_handover_intent_action" translatable="false"></string> <java-symbol type="string" name="config_satellite_emergency_handover_intent_action" /> + <!-- Whether outgoing satellite datagrams should be sent to modem in demo mode. When satellite + is enabled for demo mode, if this config is enabled, outgoing datagrams will be sent to + modem; otherwise, success results will be returned. If demo mode is disabled, outgoing + datagrams are always sent to modem. --> + <bool name="config_send_satellite_datagram_to_modem_in_demo_mode">false</bool> + <java-symbol type="bool" name="config_send_satellite_datagram_to_modem_in_demo_mode" /> + <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks will not perform handover if the target transport is out of service, or VoPS not supported. The network will be torn down on the source transport, and will be diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 709646b00e5c..3a2e50aa06e8 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -34,7 +34,7 @@ http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ --> <!-- Arab Emirates --> - <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253" /> + <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" /> <!-- Albania: 5 digits, known short codes listed --> <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> @@ -155,7 +155,7 @@ <shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" /> <!-- Israel: 4 digits, known premium codes listed --> - <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477" /> + <shortcode country="il" pattern="\\d{4}" premium="4422|4545" /> <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU: https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf --> @@ -198,9 +198,6 @@ <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" /> - <!-- Namibia: 5 digits --> - <shortcode country="na" pattern="\\d{1,5}" free="40005" /> - <!-- The Netherlands, 4 digits, known premium codes listed, plus EU --> <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" /> diff --git a/core/tests/coretests/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/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java index 3e3c77b7b21c..03c38cc137ad 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -25,9 +25,11 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import android.annotation.EnforcePermission; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; import android.os.IBinder; import android.os.RemoteException; +import android.os.test.FakePermissionEnforcer; import androidx.test.filters.SmallTest; @@ -57,7 +59,8 @@ public final class DeviceStateManagerGlobalTest { @Before public void setUp() { - mService = new TestDeviceStateManagerService(); + FakePermissionEnforcer permissionEnforcer = new FakePermissionEnforcer(); + mService = new TestDeviceStateManagerService(permissionEnforcer); mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService); assertFalse(mService.mCallbacks.isEmpty()); } @@ -261,6 +264,10 @@ public final class DeviceStateManagerGlobalTest { private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>(); + TestDeviceStateManagerService(FakePermissionEnforcer enforcer) { + super(enforcer); + } + private DeviceStateInfo getInfo() { final int mergedBaseState = mBaseStateRequest == null ? mBaseState : mBaseStateRequest.state; @@ -380,7 +387,10 @@ public final class DeviceStateManagerGlobalTest { // No-op in the test since DeviceStateManagerGlobal just calls into the system server with // no business logic around it. @Override - public void onStateRequestOverlayDismissed(boolean shouldCancelMode) {} + @EnforcePermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void onStateRequestOverlayDismissed(boolean shouldCancelMode) { + onStateRequestOverlayDismissed_enforcePermission(); + } public void setSupportedStates(int[] states) { mSupportedStates = states; diff --git a/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/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index b26d0613f3b6..07c54293111c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -16,6 +16,7 @@ package com.android.wm.shell.unfold; +import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; @@ -188,23 +189,27 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback) { - if (info.getType() == TRANSIT_CHANGE) { - // TODO (b/286928742) unfold transition handler should be part of mixed handler to - // handle merges better. - for (int i = 0; i < info.getChanges().size(); ++i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo != null - && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { - // Tasks that are always on top (e.g. bubbles), will handle their own transition - // as they are on top of everything else. So skip merging transitions here. - return; - } + if (info.getType() != TRANSIT_CHANGE) { + return; + } + if ((info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { + return; + } + // TODO (b/286928742) unfold transition handler should be part of mixed handler to + // handle merges better. + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo != null + && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { + // Tasks that are always on top (e.g. bubbles), will handle their own transition + // as they are on top of everything else. So skip merging transitions here. + return; } - // Apply changes happening during the unfold animation immediately - t.apply(); - finishCallback.onTransitionFinished(null); } + // Apply changes happening during the unfold animation immediately + t.apply(); + finishCallback.onTransitionFinished(null); } /** Whether `request` contains an unfold action. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java index 7917ba6eeb41..6d73c12dc304 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java @@ -17,6 +17,7 @@ package com.android.wm.shell.unfold; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_NONE; import static com.google.common.truth.Truth.assertThat; @@ -46,6 +47,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.transition.TransitionInfoBuilder; import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; @@ -265,6 +267,42 @@ public class UnfoldTransitionHandlerTest { verify(finishCallback).onTransitionFinished(any()); } + @Test + public void mergeAnimation_eatsDisplayOnlyTransitions() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + TransitionFinishCallback mergeCallback = mock(TransitionFinishCallback.class); + + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback); + + // Offer a keyguard unlock transition - this should NOT merge + mUnfoldTransitionHandler.mergeAnimation( + new Binder(), + new TransitionInfoBuilder(TRANSIT_CHANGE, TRANSIT_FLAG_KEYGUARD_GOING_AWAY).build(), + mock(SurfaceControl.Transaction.class), + mTransition, + mergeCallback); + verify(finishCallback, never()).onTransitionFinished(any()); + + // Offer a CHANGE-only transition - this SHOULD merge (b/278064943) + mUnfoldTransitionHandler.mergeAnimation( + new Binder(), + new TransitionInfoBuilder(TRANSIT_CHANGE).build(), + mock(SurfaceControl.Transaction.class), + mTransition, + mergeCallback); + verify(mergeCallback).onTransitionFinished(any()); + + // We should never have finished the original transition. + verify(finishCallback, never()).onTransitionFinished(any()); + } + private TransitionRequestInfo createUnfoldTransitionRequestInfo() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( @@ -372,4 +410,4 @@ public class UnfoldTransitionHandlerTest { private TransitionInfo createNonUnfoldTransitionInfo() { return new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0); } -}
\ No newline at end of file +} diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index a1b05c186ec0..a7d64231da80 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -597,7 +597,13 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, SkIRect clipBounds; if (enableClip) { uirenderer::Rect initialClipBounds; - props.getClippingRectForFlags(props.getClippingFlags(), &initialClipBounds); + const auto clipFlags = props.getClippingFlags(); + if (clipFlags) { + props.getClippingRectForFlags(clipFlags, &initialClipBounds); + } else { + // Works for RenderNode::damageSelf() + initialClipBounds.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); + } clipBounds = info.damageAccumulator ->computeClipAndTransform(initialClipBounds.toSkRect(), &transform) diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java index 985a7584ffe2..0f48abeb6882 100644 --- a/media/java/android/media/AudioHalVersionInfo.java +++ b/media/java/android/media/AudioHalVersionInfo.java @@ -22,6 +22,8 @@ import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -54,6 +56,7 @@ public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHa flag = false, prefix = "AUDIO_HAL_TYPE_", value = {AUDIO_HAL_TYPE_HIDL, AUDIO_HAL_TYPE_AIDL}) + @Retention(RetentionPolicy.SOURCE) public @interface AudioHalType {} /** AudioHalVersionInfo object of all valid Audio HAL versions. */ diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index faa7f7fa3aa1..5331046dd0e3 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -2018,6 +2018,8 @@ public final class MediaDrm implements AutoCloseable { * {@link #HDCP_V2_1}, * {@link #HDCP_V2_2}, * {@link #HDCP_V2_3} + * + * @removed mistakenly exposed previously */ @Deprecated @Retention(RetentionPolicy.SOURCE) @@ -2121,6 +2123,8 @@ public final class MediaDrm implements AutoCloseable { * {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO}, * {@link #SECURITY_LEVEL_HW_SECURE_DECODE}, * {@link #SECURITY_LEVEL_HW_SECURE_ALL} + * + * @removed mistakenly exposed previously */ @Deprecated @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java index 74ca4b858ff6..99fcaf29688c 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -267,20 +267,26 @@ public class Spatializer { HEAD_TRACKING_MODE_DISABLED, HEAD_TRACKING_MODE_RELATIVE_WORLD, HEAD_TRACKING_MODE_RELATIVE_DEVICE, - }) public @interface HeadTrackingMode {}; + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HeadTrackingMode {}; /** @hide */ @IntDef(flag = false, value = { HEAD_TRACKING_MODE_DISABLED, HEAD_TRACKING_MODE_RELATIVE_WORLD, HEAD_TRACKING_MODE_RELATIVE_DEVICE, - }) public @interface HeadTrackingModeSet {}; + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HeadTrackingModeSet {}; /** @hide */ @IntDef(flag = false, value = { HEAD_TRACKING_MODE_RELATIVE_WORLD, HEAD_TRACKING_MODE_RELATIVE_DEVICE, - }) public @interface HeadTrackingModeSupported {}; + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HeadTrackingModeSupported {}; /** * @hide diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl new file mode 100644 index 000000000000..92cc923dc9ab --- /dev/null +++ b/media/java/android/media/tv/ad/ITvAdManager.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.ad; + +/** + * Interface to the TV AD service. + * @hide + */ +interface ITvAdManager { + void startAdService(in IBinder sessionToken, int userId); +} diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl new file mode 100644 index 000000000000..b834f1b9fb92 --- /dev/null +++ b/media/java/android/media/tv/ad/ITvAdSession.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.ad; + +/** + * Sub-interface of ITvAdService which is created per session and has its own context. + * @hide + */ +oneway interface ITvAdSession { + void startAdService(); +} diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java new file mode 100644 index 000000000000..aa5a290346b0 --- /dev/null +++ b/media/java/android/media/tv/ad/TvAdManager.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.ad; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +/** + * Central system API to the overall client-side TV AD architecture, which arbitrates interaction + * between applications and AD services. + * @hide + */ +public class TvAdManager { + private static final String TAG = "TvAdManager"; + + private final ITvAdManager mService; + private final int mUserId; + + public TvAdManager(ITvAdManager service, int userId) { + mService = service; + mUserId = userId; + } + + /** + * The Session provides the per-session functionality of AD service. + */ + public static final class Session { + private final IBinder mToken; + private final ITvAdManager mService; + private final int mUserId; + + private Session(IBinder token, ITvAdManager service, int userId) { + mToken = token; + mService = service; + mUserId = userId; + } + + void startAdService() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.startAdService(mToken, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java new file mode 100644 index 000000000000..61101f07923c --- /dev/null +++ b/media/java/android/media/tv/ad/TvAdService.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.ad; + +import android.app.Service; +import android.view.KeyEvent; + +/** + * The TvAdService class represents a TV client-side advertisement service. + * @hide + */ +public abstract class TvAdService extends Service { + private static final boolean DEBUG = false; + private static final String TAG = "TvAdService"; + + /** + * Base class for derived classes to implement to provide a TV AD session. + */ + public abstract static class Session implements KeyEvent.Callback { + /** + * Starts TvAdService session. + */ + public void onStartAdService() { + } + + void startAdService() { + onStartAdService(); + } + } + + /** + * Implements the internal ITvAdService interface. + */ + public static class ITvAdSessionWrapper extends ITvAdSession.Stub { + private final Session mSessionImpl; + + public ITvAdSessionWrapper(Session mSessionImpl) { + this.mSessionImpl = mSessionImpl; + } + + @Override + public void startAdService() { + mSessionImpl.startAdService(); + } + } +} diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java new file mode 100644 index 000000000000..1a3771a9f24c --- /dev/null +++ b/media/java/android/media/tv/ad/TvAdView.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.ad; + +import android.content.Context; +import android.util.Log; +import android.view.ViewGroup; + +/** + * Displays contents of TV AD services. + * @hide + */ +public class TvAdView extends ViewGroup { + private static final String TAG = "TvAdView"; + private static final boolean DEBUG = false; + + // TODO: create session + private TvAdManager.Session mSession; + + public TvAdView(Context context) { + super(context, /* attrs = */null, /* defStyleAttr = */0); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + if (DEBUG) { + Log.d(TAG, + "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)"); + } + } + + /** + * Starts the AD service. + */ + public void startAdService() { + if (DEBUG) { + Log.d(TAG, "start"); + } + if (mSession != null) { + mSession.startAdService(); + } + } +} diff --git a/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/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt new file mode 100644 index 000000000000..d0d2dc0083a6 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.framework.common + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn + +/** + * A [BroadcastReceiver] flow for the given [intentFilter]. + */ +fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = callbackFlow { + val broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + trySend(intent) + } + } + registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED) + + awaitClose { unregisterReceiver(broadcastReceiver) } +}.conflate().flowOn(Dispatchers.Default) diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt new file mode 100644 index 000000000000..dfaf3c66ff8d --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.framework.common + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +class BroadcastReceiverFlowTest { + + private var registeredBroadcastReceiver: BroadcastReceiver? = null + + private val context = mock<Context> { + on { + registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_NOT_EXPORTED)) + } doAnswer { + registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver + null + } + } + + @Test + fun broadcastReceiverFlow_registered() = runBlocking { + val flow = context.broadcastReceiverFlow(INTENT_FILTER) + + flow.firstWithTimeoutOrNull() + + assertThat(registeredBroadcastReceiver).isNotNull() + } + + @Test + fun broadcastReceiverFlow_isCalledOnReceive() = runBlocking { + var onReceiveIsCalled = false + launch { + context.broadcastReceiverFlow(INTENT_FILTER).first { + onReceiveIsCalled = true + true + } + } + + delay(100) + registeredBroadcastReceiver!!.onReceive(context, Intent()) + delay(100) + + assertThat(onReceiveIsCalled).isTrue() + } + + private companion object { + val INTENT_FILTER = IntentFilter() + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index e9f4b5c8efc6..245fe6edd601 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1310,7 +1310,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> // Set default string with battery level in device connected situation. if (isTwsBatteryAvailable(leftBattery, rightBattery)) { stringRes = R.string.bluetooth_battery_level_untethered; - } else if (batteryLevelPercentageString != null) { + } else if (batteryLevelPercentageString != null && !shortSummary) { stringRes = R.string.bluetooth_battery_level; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 7e8fe7e09d74..d9fe7335dbcb 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -445,5 +445,6 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 2e174e267bde..c0d83c4c08d5 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -686,7 +686,8 @@ public class SettingsBackupTest { Settings.Global.Wearable.PHONE_SWITCHING_SUPPORTED, Settings.Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE, Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE, - Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED); + Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, + Settings.Global.Wearable.CONNECTIVITY_KEEP_DATA_ON); private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS = newHashSet( diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 0e9f8b153fd4..0be9f8427444 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -618,6 +618,9 @@ android_robolectric_test { instrumentation_for: "SystemUIRobo-stub", java_resource_dirs: ["tests/robolectric/config"], + plugins: [ + "dagger2-compiler", + ], } // Opt-out config for optimizing the SystemUI target using R8. diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index aebdaaba3ce1..e340209afbb8 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -107,8 +107,16 @@ flag { } flag { - name: "qs_new_pipeline" - namespace: "systemui" - description: "Use the new pipeline for Quick Settings. Should have no behavior changes." - bug: "241772429" + name: "qs_new_pipeline" + namespace: "systemui" + description: "Use the new pipeline for Quick Settings. Should have no behavior changes." + bug: "241772429" } + +flag { + name: "coroutine_tracing" + namespace: "systemui" + description: "Adds thread-local data to System UI's global coroutine scopes to " + "allow for tracing of coroutine continuations using System UI's tracinglib" + bug: "289353932" +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index fb50f69f7d9b..243751fafe5d 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 @@ -76,6 +76,8 @@ fun PinPad( val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState() val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState() val animateFailure: Boolean by viewModel.animateFailure.collectAsState() + val isDigitButtonAnimationEnabled: Boolean by + viewModel.isDigitButtonAnimationEnabled.collectAsState() val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } } LaunchedEffect(animateFailure) { @@ -94,10 +96,11 @@ fun PinPad( ) { repeat(9) { index -> DigitButton( - index + 1, - isInputEnabled, - viewModel::onPinButtonClicked, - buttonScaleAnimatables[index]::value, + digit = index + 1, + isInputEnabled = isInputEnabled, + onClicked = viewModel::onPinButtonClicked, + scaling = buttonScaleAnimatables[index]::value, + isAnimationEnabled = isDigitButtonAnimationEnabled, ) } @@ -116,10 +119,11 @@ fun PinPad( ) DigitButton( - 0, - isInputEnabled, - viewModel::onPinButtonClicked, - buttonScaleAnimatables[10]::value, + digit = 0, + isInputEnabled = isInputEnabled, + onClicked = viewModel::onPinButtonClicked, + scaling = buttonScaleAnimatables[10]::value, + isAnimationEnabled = isDigitButtonAnimationEnabled, ) ActionButton( @@ -143,15 +147,17 @@ private fun DigitButton( isInputEnabled: Boolean, onClicked: (Int) -> Unit, scaling: () -> Float, + isAnimationEnabled: Boolean, ) { PinPadButton( onClicked = { onClicked(digit) }, isEnabled = isInputEnabled, backgroundColor = MaterialTheme.colorScheme.surfaceVariant, foregroundColor = MaterialTheme.colorScheme.onSurfaceVariant, + isAnimationEnabled = isAnimationEnabled, modifier = Modifier.graphicsLayer { - val scale = scaling() + val scale = if (isAnimationEnabled) scaling() else 1f scaleX = scale scaleY = scale } @@ -195,6 +201,7 @@ private fun ActionButton( isEnabled = isInputEnabled && !isHidden, backgroundColor = backgroundColor, foregroundColor = foregroundColor, + isAnimationEnabled = true, modifier = Modifier.graphicsLayer { alpha = hiddenAlpha @@ -216,6 +223,7 @@ private fun PinPadButton( isEnabled: Boolean, backgroundColor: Color, foregroundColor: Color, + isAnimationEnabled: Boolean, modifier: Modifier = Modifier, onLongPressed: (() -> Unit)? = null, content: @Composable (contentColor: () -> Color) -> Unit, @@ -243,7 +251,7 @@ private fun PinPadButton( val cornerRadius: Dp by animateDpAsState( - if (isPressed) 24.dp else pinButtonSize / 2, + if (isAnimationEnabled && isPressed) 24.dp else pinButtonSize / 2, label = "PinButton round corners", animationSpec = tween(animDurationMillis, easing = animEasing) ) @@ -251,7 +259,7 @@ private fun PinPadButton( val containerColor: Color by animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.primary + isAnimationEnabled && isPressed -> MaterialTheme.colorScheme.primary else -> backgroundColor }, label = "Pin button container color", @@ -260,7 +268,7 @@ private fun PinPadButton( val contentColor = animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.onPrimary + isAnimationEnabled && isPressed -> MaterialTheme.colorScheme.onPrimary else -> foregroundColor }, label = "Pin button container color", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 37804682ed98..09706bed1921 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -12,7 +12,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -25,10 +24,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.FixedSizeEdgeDetector import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.transitions import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.ui.viewmodel.CommunalViewModel @@ -76,17 +77,24 @@ fun CommunalContainer( currentScene = currentScene, onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) }, transitions = sceneTransitions, + edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize) ) { scene( TransitionSceneKey.Blank, - userActions = mapOf(Swipe.Left to TransitionSceneKey.Communal) + userActions = + mapOf( + Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal + ) ) { BlankScene { showSceneTransitionLayout = false } } scene( TransitionSceneKey.Communal, - userActions = mapOf(Swipe.Right to TransitionSceneKey.Blank), + userActions = + mapOf( + Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank + ), ) { CommunalScene(viewModel, modifier = modifier) } @@ -105,14 +113,12 @@ private fun BlankScene( Box(modifier.fillMaxSize()) { Column( Modifier.fillMaxHeight() - .width(100.dp) + .width(ContainerDimensions.EdgeSwipeSize) .align(Alignment.CenterEnd) .background(Color(0x55e9f2eb)), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Default scene") - IconButton(onClick = hideSceneTransitionLayout) { Icon(Icons.Filled.Close, contentDescription = "Close button") } @@ -142,3 +148,7 @@ fun CommunalSceneKey.toTransitionSceneKey(): SceneKey { fun SceneKey.toCommunalSceneKey(): CommunalSceneKey { return this.identity as CommunalSceneKey } + +object ContainerDimensions { + val EdgeSwipeSize = 40.dp +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 05694317d879..6e18cb9cd46b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -22,6 +22,8 @@ import android.widget.FrameLayout import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height @@ -41,6 +43,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -64,6 +67,7 @@ fun CommunalHub( LazyHorizontalGrid( modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart), rows = GridCells.Fixed(CommunalContentSize.FULL.span), + contentPadding = PaddingValues(horizontal = Dimensions.Spacing), horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing), verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing), ) { @@ -91,6 +95,16 @@ fun CommunalHub( LocalContext.current.getString(R.string.button_to_open_widget_picker) ) } + + // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving + // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge + // swipe back to the blank scene. + Spacer( + Modifier.height(Dimensions.GridHeight) + .align(Alignment.CenterStart) + .width(Dimensions.Spacing) + .pointerInput(Unit) {} + ) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index b77a60b5b5d6..6153e199c24e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -18,7 +18,6 @@ package com.android.compose.animation.scene import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.movableContentOf @@ -36,7 +35,6 @@ import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.geometry.lerp import androidx.compose.ui.graphics.drawscope.scale -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.IntermediateMeasureScope import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.Placeable @@ -47,7 +45,6 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.round import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation -import com.android.compose.modifiers.thenIf import com.android.compose.ui.util.lerp /** An element on screen, that can be composed in one or more scenes. */ @@ -146,8 +143,6 @@ internal fun Modifier.element( element } - val lastSharedValues = element.lastSharedValues - val lastSceneValues = sceneValues.lastValues DisposableEffect(scene, sceneValues, element) { onDispose { @@ -160,18 +155,6 @@ internal fun Modifier.element( } } - val alpha = - remember(layoutImpl, element, scene, sceneValues) { - derivedStateOf { elementAlpha(layoutImpl, element, scene, sceneValues) } - } - val isOpaque by remember(alpha) { derivedStateOf { alpha.value == 1f } } - SideEffect { - if (isOpaque) { - lastSharedValues.alpha = 1f - lastSceneValues.alpha = 1f - } - } - val drawScale by remember(layoutImpl, element, scene, sceneValues) { derivedStateOf { getDrawScale(layoutImpl, element, scene, sceneValues) } @@ -200,14 +183,6 @@ internal fun Modifier.element( place(layoutImpl, scene, element, sceneValues, placeable, placementScope = this) } } - .thenIf(!isOpaque) { - Modifier.graphicsLayer { - val alpha = alpha.value - this.alpha = alpha - lastSharedValues.alpha = alpha - lastSceneValues.alpha = alpha - } - } .testTag(key.testTag) } @@ -325,6 +300,61 @@ private fun Modifier.modifierTransformations( } } +/** + * Whether the element is opaque or not. + * + * Important: The logic here should closely match the logic in [elementAlpha]. Note that we don't + * reuse [elementAlpha] and simply check if alpha == 1f because [isElementOpaque] is checked during + * placement and we don't want to read the transition progress in that phase. + */ +private fun isElementOpaque( + layoutImpl: SceneTransitionLayoutImpl, + element: Element, + scene: Scene, + sceneValues: Element.TargetValues, +): Boolean { + val state = layoutImpl.state.transitionState + + if (state !is TransitionState.Transition || state.fromScene == state.toScene) { + return true + } + + if (!layoutImpl.isTransitionReady(state)) { + val lastValue = + sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified } + ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f + + return lastValue == 1f + } + + val fromScene = state.fromScene + val toScene = state.toScene + val fromValues = element.sceneValues[fromScene] + val toValues = element.sceneValues[toScene] + + if (fromValues == null && toValues == null) { + error("This should not happen, element $element is neither in $fromScene or $toScene") + } + + val isSharedElement = fromValues != null && toValues != null + if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { + return true + } + + return layoutImpl.transitions + .transitionSpec(fromScene, toScene) + .transformations(element.key, scene.key) + .alpha == null +} + +/** + * Whether the element is opaque or not. + * + * Important: The logic here should closely match the logic in [isElementOpaque]. Note that we don't + * reuse [elementAlpha] in [isElementOpaque] and simply check if alpha == 1f because + * [isElementOpaque] is checked during placement and we don't want to read the transition progress + * in that phase. + */ private fun elementAlpha( layoutImpl: SceneTransitionLayoutImpl, element: Element, @@ -446,6 +476,8 @@ private fun IntermediateMeasureScope.place( } val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero) + val lastSharedValues = element.lastSharedValues + val lastValues = sceneValues.lastValues val targetOffset = computeValue( layoutImpl, @@ -456,16 +488,31 @@ private fun IntermediateMeasureScope.place( idleValue = targetOffsetInScene, currentValue = { currentOffset }, lastValue = { - sceneValues.lastValues.offset.takeIf { it.isSpecified } - ?: element.lastSharedValues.offset.takeIf { it.isSpecified } - ?: currentOffset + lastValues.offset.takeIf { it.isSpecified } + ?: lastSharedValues.offset.takeIf { it.isSpecified } ?: currentOffset }, ::lerp, ) - element.lastSharedValues.offset = targetOffset - sceneValues.lastValues.offset = targetOffset - placeable.place((targetOffset - currentOffset).round()) + lastSharedValues.offset = targetOffset + lastValues.offset = targetOffset + + val offset = (targetOffset - currentOffset).round() + if (isElementOpaque(layoutImpl, element, scene, sceneValues)) { + // TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not + // animated once b/305195729 is fixed. Test that drawing is not invalidated in that + // case. + placeable.place(offset) + lastSharedValues.alpha = 1f + lastValues.alpha = 1f + } else { + placeable.placeWithLayer(offset) { + val alpha = elementAlpha(layoutImpl, element, scene, sceneValues) + this.alpha = alpha + lastSharedValues.alpha = alpha + lastValues.alpha = alpha + } + } } } @@ -527,21 +574,17 @@ private inline fun <T> computeValue( error("This should not happen, element $element is neither in $fromScene or $toScene") } - // TODO(b/291053278): Handle overscroll correctly. We should probably coerce between [0f, 1f] - // here and consume overflows at drawing time, somehow reusing Compose OverflowEffect or some - // similar mechanism. - val transitionProgress = state.progress - // The element is shared: interpolate between the value in fromScene and the value in toScene. // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. val isSharedElement = fromValues != null && toValues != null if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { - return lerp( - sceneValue(fromValues!!), - sceneValue(toValues!!), - transitionProgress, - ) + val start = sceneValue(fromValues!!) + val end = sceneValue(toValues!!) + + // Make sure we don't read progress if values are the same and we don't need to interpolate, + // so we don't invalidate the phase where this is read. + return if (start == end) start else lerp(start, end, state.progress) } val transformation = @@ -576,8 +619,15 @@ private inline fun <T> computeValue( idleValue, ) + // Make sure we don't read progress if values are the same and we don't need to interpolate, so + // we don't invalidate the phase where this is read. + if (targetValue == idleValue) { + return targetValue + } + + val progress = state.progress // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range. - val rangeProgress = transformation.range?.progress(transitionProgress) ?: transitionProgress + val rangeProgress = transformation.range?.progress(progress) ?: progress // Interpolate between the value at rest and the value before entering/after leaving. val isEntering = scene.key == toScene diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt index 9d71801be25b..838cb3bd5fba 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt @@ -39,13 +39,13 @@ import kotlinx.coroutines.launch @VisibleForTesting class SceneGestureHandler( - private val layoutImpl: SceneTransitionLayoutImpl, + internal val layoutImpl: SceneTransitionLayoutImpl, internal val orientation: Orientation, private val coroutineScope: CoroutineScope, ) { val draggable: DraggableHandler = SceneDraggableHandler(this) - private var transitionState + internal var transitionState get() = layoutImpl.state.transitionState set(value) { layoutImpl.state.transitionState = value @@ -58,7 +58,7 @@ class SceneGestureHandler( * Note: the initialScene here does not matter, it's only used for initializing the transition * and will be replaced when a drag event starts. */ - private val swipeTransition = SwipeTransition(initialScene = currentScene) + internal val swipeTransition = SwipeTransition(initialScene = currentScene) internal val currentScene: Scene get() = layoutImpl.scene(transitionState.currentScene) @@ -415,7 +415,7 @@ class SceneGestureHandler( } } - private class SwipeTransition(initialScene: Scene) : TransitionState.Transition { + internal class SwipeTransition(initialScene: Scene) : TransitionState.Transition { var _currentScene by mutableStateOf(initialScene) override val currentScene: SceneKey get() = _currentScene.key @@ -598,9 +598,29 @@ class SceneNestedScrollHandler( return PriorityNestedScrollConnection( canStartPreScroll = { offsetAvailable, offsetBeforeStart -> canChangeScene = offsetBeforeStart == Offset.Zero - gestureHandler.isDrivingTransition && + + val canInterceptSwipeTransition = canChangeScene && - offsetAvailable.toAmount() != 0f + gestureHandler.isDrivingTransition && + offsetAvailable.toAmount() != 0f + if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false + + val progress = gestureHandler.swipeTransition.progress + val threshold = gestureHandler.layoutImpl.transitionInterceptionThreshold + fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold + + // The transition is always between 0 and 1. If it is close to either of these + // intervals, we want to go directly to the TransitionState.Idle. + // The progress value can go beyond this range in the case of overscroll. + val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f) + if (shouldSnapToIdle) { + gestureHandler.swipeTransition.stopOffsetAnimation() + gestureHandler.transitionState = + TransitionState.Idle(gestureHandler.swipeTransition.currentScene) + } + + // Start only if we cannot consume this event + !shouldSnapToIdle }, canStartPostScroll = { offsetAvailable, offsetBeforeStart -> val amount = offsetAvailable.toAmount() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index efdfe7a7921e..9c31445e1b96 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -16,6 +16,7 @@ package com.android.compose.animation.scene +import androidx.annotation.FloatRange import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable import androidx.compose.runtime.State @@ -41,6 +42,8 @@ import androidx.compose.ui.platform.LocalDensity * @param transitions the definition of the transitions used to animate a change of scene. * @param state the observable state of this layout. * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any. + * @param transitionInterceptionThreshold used during a scene transition. For the scene to be + * intercepted, the progress value must be above the threshold, and below (1 - threshold). * @param scenes the configuration of the different scenes of this layout. */ @Composable @@ -51,6 +54,7 @@ fun SceneTransitionLayout( modifier: Modifier = Modifier, state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) }, edgeDetector: EdgeDetector = DefaultEdgeDetector, + @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f, scenes: SceneTransitionLayoutScope.() -> Unit, ) { val density = LocalDensity.current @@ -63,6 +67,7 @@ fun SceneTransitionLayout( state = state, density = density, edgeDetector = edgeDetector, + transitionInterceptionThreshold = transitionInterceptionThreshold, coroutineScope = coroutineScope, ) } @@ -71,6 +76,7 @@ fun SceneTransitionLayout( layoutImpl.transitions = transitions layoutImpl.density = density layoutImpl.edgeDetector = edgeDetector + layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold layoutImpl.setScenes(scenes) layoutImpl.setCurrentScene(currentScene) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 0b06953bc8e2..94f2737039f4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -50,6 +50,7 @@ class SceneTransitionLayoutImpl( internal val state: SceneTransitionLayoutState, density: Density, edgeDetector: EdgeDetector, + transitionInterceptionThreshold: Float, coroutineScope: CoroutineScope, ) { internal val scenes = SnapshotStateMap<SceneKey, Scene>() @@ -62,6 +63,7 @@ class SceneTransitionLayoutImpl( internal var transitions by mutableStateOf(transitions) internal var density: Density by mutableStateOf(density) internal var edgeDetector by mutableStateOf(edgeDetector) + internal var transitionInterceptionThreshold by mutableStateOf(transitionInterceptionThreshold) private val horizontalGestureHandler: SceneGestureHandler private val verticalGestureHandler: SceneGestureHandler diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt new file mode 100644 index 000000000000..6401bb3d994a --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.intermediateLayout +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ElementTest { + @get:Rule val rule = createComposeRule() + + @Composable + @OptIn(ExperimentalComposeUiApi::class) + private fun SceneScope.Element( + key: ElementKey, + size: Dp, + offset: Dp, + modifier: Modifier = Modifier, + onLayout: () -> Unit = {}, + onPlacement: () -> Unit = {}, + ) { + Box( + modifier + .offset(offset) + .element(key) + .intermediateLayout { measurable, constraints -> + onLayout() + val placement = measurable.measure(constraints) + layout(placement.width, placement.height) { + onPlacement() + placement.place(0, 0) + } + } + .size(size) + ) + } + + @Test + fun staticElements_noLayout_noPlacement() { + val nFrames = 20 + val layoutSize = 100.dp + val elementSize = 50.dp + val elementOffset = 20.dp + + var fooLayouts = 0 + var fooPlacements = 0 + var barLayouts = 0 + var barPlacements = 0 + + rule.testTransition( + fromSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element( + TestElements.Foo, + elementSize, + elementOffset, + onLayout = { fooLayouts++ }, + onPlacement = { fooPlacements++ }, + ) + + // Transformed element + Element( + TestElements.Bar, + elementSize, + elementOffset, + onLayout = { barLayouts++ }, + onPlacement = { barPlacements++ }, + ) + } + }, + toSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element(TestElements.Foo, elementSize, elementOffset) + } + }, + transition = { + spec = tween(nFrames * 16) + + // no-op transformations. + translate(TestElements.Bar, x = 0.dp, y = 0.dp) + scaleSize(TestElements.Bar, width = 1f, height = 1f) + }, + ) { + var numberOfLayoutsAfterOneAnimationFrame = 0 + var numberOfPlacementsAfterOneAnimationFrame = 0 + + fun assertNumberOfLayoutsAndPlacements() { + assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + assertThat(fooPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame) + assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + assertThat(barPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame) + } + + at(16) { + // Capture the number of layouts and placements that happened after 1 animation + // frame. + numberOfLayoutsAfterOneAnimationFrame = fooLayouts + numberOfPlacementsAfterOneAnimationFrame = fooPlacements + } + repeat(nFrames - 2) { i -> + // Ensure that all animation frames (except the final one) don't relayout or replace + // static (shared or transformed) elements. + at(32L + i * 16) { assertNumberOfLayoutsAndPlacements() } + } + } + } + + @Test + fun onlyMovingElements_noLayout_onlyPlacement() { + val nFrames = 20 + val layoutSize = 100.dp + val elementSize = 50.dp + + var fooLayouts = 0 + var fooPlacements = 0 + var barLayouts = 0 + var barPlacements = 0 + + rule.testTransition( + fromSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element( + TestElements.Foo, + elementSize, + offset = 0.dp, + onLayout = { fooLayouts++ }, + onPlacement = { fooPlacements++ }, + ) + + // Transformed element + Element( + TestElements.Bar, + elementSize, + offset = 0.dp, + onLayout = { barLayouts++ }, + onPlacement = { barPlacements++ }, + ) + } + }, + toSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element(TestElements.Foo, elementSize, offset = 20.dp) + } + }, + transition = { + spec = tween(nFrames * 16) + + // Only translate Bar. + translate(TestElements.Bar, x = 20.dp, y = 20.dp) + scaleSize(TestElements.Bar, width = 1f, height = 1f) + }, + ) { + var numberOfLayoutsAfterOneAnimationFrame = 0 + var lastNumberOfPlacements = 0 + + fun assertNumberOfLayoutsAndPlacements() { + // The number of layouts have not changed. + assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + + // The number of placements have increased. + assertThat(fooPlacements).isGreaterThan(lastNumberOfPlacements) + assertThat(barPlacements).isGreaterThan(lastNumberOfPlacements) + lastNumberOfPlacements = fooPlacements + } + + at(16) { + // Capture the number of layouts and placements that happened after 1 animation + // frame. + numberOfLayoutsAfterOneAnimationFrame = fooLayouts + lastNumberOfPlacements = fooPlacements + } + repeat(nFrames - 2) { i -> + // Ensure that all animation frames (except the final one) only replaced the + // elements. + at(32L + i * 16) { assertNumberOfLayoutsAndPlacements() } + } + } + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt index 7ab2096b3d88..49ef31b16d73 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -69,6 +69,8 @@ class SceneGestureHandlerTest { scene(SceneC) { Text("SceneC") } } + val transitionInterceptionThreshold = 0.05f + val sceneGestureHandler = SceneGestureHandler( layoutImpl = @@ -79,6 +81,7 @@ class SceneGestureHandlerTest { state = layoutState, density = Density(1f), edgeDetector = DefaultEdgeDetector, + transitionInterceptionThreshold = transitionInterceptionThreshold, coroutineScope = coroutineScope, ) .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }, @@ -107,6 +110,9 @@ class SceneGestureHandlerTest { val transitionState: TransitionState get() = layoutState.transitionState + val progress: Float + get() = (transitionState as Transition).progress + fun advanceUntilIdle() { coroutineScope.testScheduler.advanceUntilIdle() } @@ -145,13 +151,12 @@ class SceneGestureHandlerTest { fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest { draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition draggable.onDelta(pixels = deltaInPixels10) - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) draggable.onDelta(pixels = deltaInPixels10) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) } @Test @@ -257,8 +262,7 @@ class SceneGestureHandlerTest { ) assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) assertThat(consumed).isEqualTo(offsetY10) } @@ -282,13 +286,12 @@ class SceneGestureHandlerTest { nestedScroll.scroll(available = offsetY10) assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) // start intercept preScroll val consumed = nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) // do nothing on postScroll nestedScroll.onPostScroll( @@ -296,13 +299,71 @@ class SceneGestureHandlerTest { available = Offset.Zero, source = NestedScrollSource.Drag ) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.3f) + assertThat(progress).isEqualTo(0.3f) assertScene(currentScene = SceneA, isIdle = false) } + private suspend fun TestGestureScope.preScrollAfterSceneTransition( + firstScroll: Float, + secondScroll: Float + ) { + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) + // start scene transition + nestedScroll.scroll(available = Offset(0f, SCREEN_SIZE * firstScroll)) + + // stop scene transition (start the "stop animation") + nestedScroll.onPreFling(available = Velocity.Zero) + + // a pre scroll event, that could be intercepted by SceneGestureHandler + nestedScroll.onPreScroll(Offset(0f, SCREEN_SIZE * secondScroll), NestedScrollSource.Drag) + } + + // Float tolerance for comparisons + private val tolerance = 0.00001f + + @Test + fun scrollAndFling_scrollLessThanInterceptable_goToIdleOnCurrentScene() = runGestureTest { + val first = transitionInterceptionThreshold - tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertScene(SceneA, isIdle = true) + } + + @Test + fun scrollAndFling_scrollMinInterceptable_interceptPreScrollEvents() = runGestureTest { + val first = transitionInterceptionThreshold + tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertThat(progress).isWithin(tolerance).of(first + second) + } + + @Test + fun scrollAndFling_scrollMaxInterceptable_interceptPreScrollEvents() = runGestureTest { + val first = 1f - transitionInterceptionThreshold - tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertThat(progress).isWithin(tolerance).of(first + second) + } + + @Test + fun scrollAndFling_scrollMoreThanInterceptable_goToIdleOnNextScene() = runGestureTest { + val first = 1f - transitionInterceptionThreshold + tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertScene(SceneC, isIdle = true) + } + @Test fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) @@ -444,24 +505,23 @@ class SceneGestureHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = Always) draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition draggable.onDelta(deltaInPixels10) - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) // now we can intercept the scroll events nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) // this should be ignored, we are scrolling now! draggable.onDragStopped(velocityThreshold) assertScene(currentScene = SceneA, isIdle = false) nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.3f) + assertThat(progress).isEqualTo(0.3f) nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.4f) + assertThat(progress).isEqualTo(0.4f) nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold)) assertScene(currentScene = SceneC, isIdle = false) diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml index 08eccbbe9669..4336ccc70c33 100644 --- a/packages/SystemUI/res/layout/bluetooth_device_item.xml +++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml @@ -21,7 +21,7 @@ style="@style/BluetoothTileDialog.Device" android:layout_width="match_parent" android:layout_height="@dimen/bluetooth_dialog_device_height" - android:paddingEnd="24dp" + android:paddingEnd="0dp" android:paddingStart="20dp" android:layout_marginBottom="4dp"> @@ -86,6 +86,7 @@ android:id="@+id/gear_icon_image" android:layout_width="0dp" android:layout_height="24dp" + android:paddingEnd="24dp" android:src="@drawable/ic_settings_24dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml index c11a18b795a1..af29cada2657 100644 --- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml +++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml @@ -120,6 +120,16 @@ android:visibility="gone" app:constraint_referenced_ids="ic_arrow,see_all_text" /> + <View + android:id="@+id/see_all_clickable_row" + android:layout_width="0dp" + android:layout_height="0dp" + android:contentDescription="@string/accessibility_bluetooth_device_settings_see_all" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/device_list" + app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" /> + <ImageView android:id="@+id/ic_arrow" android:layout_marginStart="36dp" @@ -141,6 +151,8 @@ android:maxLines="1" android:ellipsize="end" android:gravity="center_vertical" + android:importantForAccessibility="no" + android:clickable="false" android:layout_marginStart="0dp" android:paddingStart="20dp" android:text="@string/see_all_bluetooth_devices" @@ -158,6 +170,16 @@ android:visibility="gone" app:constraint_referenced_ids="ic_add,pair_new_device_text" /> + <View + android:id="@+id/pair_new_device_clickable_row" + android:layout_width="0dp" + android:layout_height="0dp" + android:contentDescription="@string/accessibility_bluetooth_device_settings_pair_new_device" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/see_all_text" + app:layout_constraintBottom_toTopOf="@+id/done_button" /> + <ImageView android:id="@+id/ic_add" android:layout_width="24dp" @@ -180,6 +202,8 @@ android:maxLines="1" android:ellipsize="end" android:gravity="center_vertical" + android:importantForAccessibility="no" + android:clickable="false" android:layout_marginStart="0dp" android:paddingStart="20dp" android:text="@string/pair_new_bluetooth_devices" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9a3c6d5b322f..3163533a3f4d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -467,6 +467,10 @@ <!-- Content description of the bluetooth device settings gear icon. [CHAR LIMIT=NONE] --> <string name="accessibility_bluetooth_device_settings_gear">Click to configure device detail</string> + <!-- Content description of the bluetooth device settings see all. [CHAR LIMIT=NONE] --> + <string name="accessibility_bluetooth_device_settings_see_all">Click to see all devices</string> + <!-- Content description of the bluetooth device settings pair new device. [CHAR LIMIT=NONE] --> + <string name="accessibility_bluetooth_device_settings_pair_new_device">Click to pair new device</string> <!-- Content description of the battery when battery state is unknown for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_battery_unknown">Battery percentage unknown.</string> @@ -640,6 +644,10 @@ <string name="quick_settings_bluetooth_device_connected">Connected</string> <!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]--> <string name="quick_settings_bluetooth_device_saved">Saved</string> + <!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]--> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect">disconnect</string> + <!-- QuickSettings: Accessibility label to activate a device [CHAR LIMIT=NONE]--> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate">activate</string> <!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]--> <string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt index 6082fb93aa26..8ad32b4f1695 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt @@ -25,7 +25,11 @@ enum class FingerprintSensorType { UDFPS_ULTRASONIC, UDFPS_OPTICAL, POWER_BUTTON, - HOME_BUTTON + HOME_BUTTON; + + fun isUdfps(): Boolean { + return (this == UDFPS_OPTICAL) || (this == UDFPS_ULTRASONIC) + } } /** Convert [this] to corresponding [FingerprintSensorType] */ diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 6b390b1191d2..c02ffa788d48 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -39,10 +39,10 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.DisplaySpecific import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW import com.android.systemui.flags.Flags.REGION_SAMPLING import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.LogBuffer @@ -298,7 +298,7 @@ constructor( object : KeyguardUpdateMonitorCallback() { override fun onKeyguardVisibilityChanged(visible: Boolean) { isKeyguardVisible = visible - if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { if (!isKeyguardVisible) { clock?.run { smallClock.animations.doze(if (isDozing) 1f else 0f) @@ -345,7 +345,7 @@ constructor( parent.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { listenForDozing(this) - if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { listenForDozeAmountTransition(this) listenForAnyStateToAodTransition(this) } else { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 32f9c3057753..36e18b36934c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -46,6 +46,7 @@ import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder; import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager; import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel; @@ -62,6 +63,7 @@ import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -105,6 +107,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final DozeParameters mDozeParameters; private final ScreenOffAnimationController mScreenOffAnimationController; private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore; + private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker; private FrameLayout mSmallClockFrame; // top aligned clock private FrameLayout mLargeClockFrame; // centered clock @@ -179,6 +182,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS LockscreenSmartspaceController smartspaceController, ConfigurationController configurationController, ScreenOffAnimationController screenOffAnimationController, + StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker, KeyguardUnlockAnimationController keyguardUnlockAnimationController, SecureSettings secureSettings, @Main DelayableExecutor uiExecutor, @@ -202,6 +206,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mSmartspaceController = smartspaceController; mConfigurationController = configurationController; mScreenOffAnimationController = screenOffAnimationController; + mIconViewBindingFailureTracker = iconViewBindingFailureTracker; mSecureSettings = secureSettings; mUiExecutor = uiExecutor; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; @@ -362,7 +367,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } int getNotificationIconAreaHeight() { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { return 0; } else if (NotificationIconContainerRefactor.isEnabled()) { return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0; @@ -590,7 +595,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } private void updateAodIcons() { - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { NotificationIconContainer nic = (NotificationIconContainer) mView.findViewById( com.android.systemui.res.R.id.left_aligned_notification_icon_container); @@ -605,6 +610,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mAodIconsViewModel, mConfigurationState, mConfigurationController, + mIconViewBindingFailureTracker, mAodIconViewStore); final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility( nic, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 758d1fef6e2e..87d937bc45fb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -51,10 +51,9 @@ import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.Dumpable; import com.android.systemui.animation.ViewHierarchyAnimator; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.plugins.ClockController; @@ -100,7 +99,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; - private final FeatureFlags mFeatureFlags; private final InteractionJankMonitor mInteractionJankMonitor; private final Rect mClipBounds = new Rect(); private final KeyguardInteractor mKeyguardInteractor; @@ -136,7 +134,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, KeyguardLogger logger, - FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, KeyguardInteractor keyguardInteractor, KeyguardTransitionInteractor keyguardTransitionInteractor, @@ -149,9 +146,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mConfigurationController = configurationController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true, - featureFlags, logger.getBuffer()); + logger.getBuffer()); mInteractionJankMonitor = interactionJankMonitor; - mFeatureFlags = featureFlags; mDumpManager = dumpManager; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; @@ -188,7 +184,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } mDumpManager.registerDumpable(getInstanceName(), this); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { startCoroutines(EmptyCoroutineContext.INSTANCE); } } @@ -454,7 +450,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(layout); int guideline; - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { guideline = R.id.split_shade_guideline; } else { guideline = R.id.qs_edge_guideline; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index d524e4a8c408..ef6514447561 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -23,8 +23,7 @@ import android.util.Property; import android.view.View; import com.android.app.animation.Interpolators; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.statusbar.StatusBarState; @@ -55,7 +54,6 @@ public class KeyguardVisibilityHelper { private boolean mKeyguardViewVisibilityAnimating; private boolean mLastOccludedState = false; private final AnimationProperties mAnimationProperties = new AnimationProperties(); - private final FeatureFlags mFeatureFlags; private final LogBuffer mLogBuffer; public KeyguardVisibilityHelper(View view, @@ -63,14 +61,12 @@ public class KeyguardVisibilityHelper { DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, boolean animateYPos, - FeatureFlags featureFlags, LogBuffer logBuffer) { mView = view; mKeyguardStateController = keyguardStateController; mDozeParameters = dozeParameters; mScreenOffAnimationController = screenOffAnimationController; mAnimateYPos = animateYPos; - mFeatureFlags = featureFlags; mLogBuffer = logBuffer; } @@ -167,7 +163,7 @@ public class KeyguardVisibilityHelper { animProps, true /* animate */); } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { log("Using GoneToAodTransition"); mKeyguardViewVisibilityAnimating = false; } else { diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index ee3a55f51679..7769dd9dc9ab 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 @@ -108,6 +108,9 @@ interface AuthenticationRepository { /** The minimal length of a pattern. */ val minPatternLength: Int + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> + /** * Returns the currently-configured authentication method. This determines how the * authentication challenge needs to be completed in order to unlock an otherwise locked device. @@ -212,6 +215,12 @@ constructor( override val minPatternLength: Int = LockPatternUtils.MIN_LOCK_PATTERN_SIZE + override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + refreshingFlow( + initialValue = true, + getFreshValue = { userId -> lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) }, + ) + override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return withContext(backgroundDispatcher) { blockingAuthenticationMethodInternal(userRepository.selectedUserId) diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 1ede5301e751..5eefbf5353d3 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -145,6 +145,9 @@ constructor( val authenticationChallengeResult: SharedFlow<Boolean> = repository.authenticationChallengeResult + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled + private var throttlingCountdownJob: Job? = null init { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 4e1cddc63530..ff36839460be 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 @@ -92,6 +92,10 @@ constructor( /** Whether the pattern should be visible for the currently-selected user. */ val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + authenticationInteractor.isPinEnhancedPrivacyEnabled + /** Whether the user switcher should be displayed within the bouncer UI on large screens. */ val isUserSwitcherVisible: Boolean get() = repository.isUserSwitcherVisible diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 2ed0d5d2f6c1..b2b8049e3cff 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 @@ -84,6 +84,19 @@ class PinBouncerViewModel( override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message + /** + * Whether the digit buttons should be animated when touched. Note that this doesn't affect the + * delete or enter buttons; those should always animate. + */ + val isDigitButtonAnimationEnabled: StateFlow<Boolean> = + interactor.isPinEnhancedPrivacyEnabled + .map { !it } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = !interactor.isPinEnhancedPrivacyEnabled.value, + ) + /** Notifies that the user clicked on a PIN button with the given digit value. */ fun onPinButtonClicked(input: Int) { val pinInput = mutablePinInput.value diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 9fc86adce091..1dcc5402e747 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -59,6 +59,7 @@ import com.android.systemui.flags.FlagsModule; import com.android.systemui.keyboard.KeyboardModule; import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule; import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule; +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; @@ -120,6 +121,7 @@ import com.android.systemui.statusbar.policy.PolicyModule; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule; import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule; +import com.android.systemui.statusbar.ui.binder.StatusBarViewBinderModule; import com.android.systemui.statusbar.window.StatusBarWindowModule; import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule; import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule; @@ -189,6 +191,7 @@ import javax.inject.Named; KeyEventRepositoryModule.class, KeyboardModule.class, KeyguardBlueprintModule.class, + KeyguardSectionsModule.class, LetterboxModule.class, LogModule.class, MediaProjectionModule.class, @@ -215,6 +218,7 @@ import javax.inject.Named; StatusBarModule.class, StatusBarPipelineModule.class, StatusBarPolicyModule.class, + StatusBarViewBinderModule.class, StatusBarWindowModule.class, SystemPropertiesFlagsModule.class, SysUIConcurrencyModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index c3f352932675..298811baba6c 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -29,6 +29,7 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest @@ -46,6 +47,7 @@ import kotlinx.coroutines.launch * Device entry occurs when the user successfully dismisses (or bypasses) the lockscreen, regardless * of the authentication method used. */ +@ExperimentalCoroutinesApi @SysUISingleton class DeviceEntryInteractor @Inject diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt new file mode 100644 index 000000000000..72b9da669360 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Encapsulates business logic for device entry under-display fingerprint state changes. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DeviceEntryUdfpsInteractor +@Inject +constructor( + // TODO (b/309655554): create & use interactors for these repositories + fingerprintPropertyRepository: FingerprintPropertyRepository, + fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, + biometricSettingsRepository: BiometricSettingsRepository, +) { + /** Whether the device supports an under display fingerprint sensor. */ + val isUdfpsSupported: Flow<Boolean> = + fingerprintPropertyRepository.sensorType.map { it.isUdfps() } + + /** Whether the under-display fingerprint sensor is enrolled and enabled for device entry. */ + val isUdfpsEnrolledAndEnabled: Flow<Boolean> = + combine(isUdfpsSupported, biometricSettingsRepository.isFingerprintEnrolledAndEnabled) { + udfps, + fpEnrolledAndEnabled -> + udfps && fpEnrolledAndEnabled + } + /** Whether the under display fingerprint sensor is currently running. */ + val isListeningForUdfps = + isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + fingerprintAuthRepository.isRunning + } else { + flowOf(false) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/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..ad3d6d646f46 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -254,14 +254,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/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 8b93b171d241..331d892304b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -55,6 +55,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule; import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl; +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; @@ -94,6 +95,7 @@ import kotlinx.coroutines.CoroutineDispatcher; KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class}, includes = { + DeviceEntryIconTransitionModule.class, FalsingModule.class, KeyguardDataQuickAffordanceModule.class, KeyguardRepositoryModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt index 8bf2bc395f88..cc1cf911f1c1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt @@ -50,14 +50,18 @@ constructor( private val configurationRepository: ConfigurationRepository, private val keyguardInteractor: KeyguardInteractor, ) { - val udfpsBurnInXOffset: StateFlow<Int> = + val deviceEntryIconXOffset: StateFlow<Int> = burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true) - val udfpsBurnInYOffset: StateFlow<Int> = + val deviceEntryIconYOffset: StateFlow<Int> = burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false) - val udfpsBurnInProgress: StateFlow<Float> = + val udfpsProgress: StateFlow<Float> = keyguardInteractor.dozeTimeTick .mapLatest { burnInHelperWrapper.burnInProgressOffset() } - .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset()) + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + burnInHelperWrapper.burnInProgressOffset() + ) val keyguardBurnIn: Flow<BurnInModel> = combine( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index a331a668e135..858440185568 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -113,7 +113,9 @@ constructor( } companion object { - val TO_LOCKSCREEN_DURATION = 500.milliseconds private val DEFAULT_DURATION = 500.milliseconds + val TO_LOCKSCREEN_DURATION = 500.milliseconds + val TO_GONE_DURATION = DEFAULT_DURATION + val TO_OCCLUDED_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index e9719e7d584e..eca7088c079a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -26,11 +26,11 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds @SysUISingleton class FromDozingTransitionInteractor @@ -97,5 +97,6 @@ constructor( companion object { private val DEFAULT_DURATION = 500.milliseconds + val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index eace0c70cb5b..bd73d60cda29 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -138,6 +138,6 @@ constructor( companion object { private val DEFAULT_DURATION = 500.milliseconds val TO_DREAMING_DURATION = 933.milliseconds - val TO_AOD_DURATION = 1100.milliseconds + val TO_AOD_DURATION = 1300.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index ea40ba0376d4..152d2172ee4c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -391,5 +391,7 @@ constructor( val TO_DREAMING_DURATION = 933.milliseconds val TO_OCCLUDED_DURATION = 450.milliseconds val TO_AOD_DURATION = 500.milliseconds + val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION + val TO_GONE_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index dec38b504ee1..6a8555cb7f6b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -142,9 +142,7 @@ constructor( ::toTriple ) .collect { (isAsleep, lastStartedStep, isAodAvailable) -> - if ( - lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep - ) { + if (lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep) { startTransitionTo( if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING ) @@ -187,5 +185,6 @@ constructor( companion object { private val DEFAULT_DURATION = 500.milliseconds val TO_LOCKSCREEN_DURATION = 933.milliseconds + val TO_AOD_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 24b666185ce2..5f246e181c26 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -255,5 +255,7 @@ constructor( private val DEFAULT_DURATION = 300.milliseconds val TO_GONE_DURATION = 500.milliseconds val TO_GONE_SHORT_DURATION = 200.milliseconds + val TO_AOD_DURATION = DEFAULT_DURATION + val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt index c0308e6c5759..f5cd7676e4b1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt @@ -51,14 +51,14 @@ constructor( val scaleForResolution = configRepo.scaleForResolution /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */ - val burnInOffsets: Flow<BurnInOffsets> = + val burnInOffsets: Flow<Offsets> = combine( keyguardInteractor.dozeAmount, - burnInInteractor.udfpsBurnInXOffset, - burnInInteractor.udfpsBurnInYOffset, - burnInInteractor.udfpsBurnInProgress + burnInInteractor.deviceEntryIconXOffset, + burnInInteractor.deviceEntryIconYOffset, + burnInInteractor.udfpsProgress ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress -> - BurnInOffsets( + Offsets( intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX), intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY), floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress), @@ -86,8 +86,8 @@ constructor( .onStart { emit(0f) } } -data class BurnInOffsets( - val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount - val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount - val burnInProgress: Float, // current progress based on the aodTransitionAmount +data class Offsets( + val x: Int, // current x burn in offset based on the aodTransitionAmount + val y: Int, // current y burn in offset based on the aodTransitionAmount + val progress: Float, // current progress based on the aodTransitionAmount ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt new file mode 100644 index 000000000000..23642a741fb8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.keyguard.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the keyguard shade migration nssl flag state. */ +@Suppress("NOTHING_TO_INLINE") +object KeyguardShadeMigrationNssl { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.keyguardShadeMigrationNssl() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt index 9d7477c13be6..d5ad7ab0d0d1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt @@ -105,4 +105,9 @@ class KeyguardTransitionAnimationFlow( } .filterNotNull() } + + /** Immediately (after 1ms) emits the given value for every step of the KeyguardTransition. */ + fun immediatelyTransitionTo(value: Float): Flow<Float> { + return createFlow(duration = 1.milliseconds, onStep = { value }, onFinish = { value }) + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index e82ea7fecd05..a8b28bcfbbc0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -24,6 +24,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.common.ui.view.LongPressHandlingView import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager @@ -34,19 +36,24 @@ import kotlinx.coroutines.launch object DeviceEntryIconViewBinder { /** - * Updates UI for the device entry icon view (lock, unlock and fingerprint icons) and its - * background. + * Updates UI for: + * - device entry containing view (parent view for the below views) + * - long-press handling view (transparent, no UI) + * - foreground icon view (lock/unlock/fingerprint) + * - background view (optional) */ @SuppressLint("ClickableViewAccessibility") @JvmStatic fun bind( view: DeviceEntryIconView, viewModel: DeviceEntryIconViewModel, + fgViewModel: DeviceEntryForegroundViewModel, + bgViewModel: DeviceEntryBackgroundViewModel, falsingManager: FalsingManager, ) { - val iconView = view.iconView - val bgView = view.bgView val longPressHandlingView = view.longPressHandlingView + val fgIconView = view.iconView + val bgView = view.bgView longPressHandlingView.listener = object : LongPressHandlingView.Listener { override fun onLongPressDetected(view: View, x: Int, y: Int) { @@ -56,45 +63,69 @@ object DeviceEntryIconViewBinder { viewModel.onLongPress() } } + view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { + // Repeat on CREATED so that the view will always observe the entire + // GONE => AOD transition (even though the view may not be visible until the middle + // of the transition. + repeatOnLifecycle(Lifecycle.State.CREATED) { launch { - viewModel.iconViewModel.collect { iconViewModel -> - iconView.setImageState( - view.getIconState(iconViewModel.type, iconViewModel.useAodVariant), - /* merge */ false - ) - iconView.imageTintList = ColorStateList.valueOf(iconViewModel.tint) - iconView.alpha = iconViewModel.alpha - iconView.setPadding( - iconViewModel.padding, - iconViewModel.padding, - iconViewModel.padding, - iconViewModel.padding, - ) + viewModel.isLongPressEnabled.collect { isEnabled -> + longPressHandlingView.setLongPressHandlingEnabled(isEnabled) } } launch { - viewModel.backgroundViewModel.collect { bgViewModel -> - bgView.alpha = bgViewModel.alpha - bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint) + viewModel.accessibilityDelegateHint.collect { hint -> + view.accessibilityHintType = hint } } launch { - viewModel.burnInViewModel.collect { burnInViewModel -> - view.translationX = burnInViewModel.x.toFloat() - view.translationY = burnInViewModel.y.toFloat() - view.aodFpDrawable.progress = burnInViewModel.progress + viewModel.useBackgroundProtection.collect { useBackgroundProtection -> + if (useBackgroundProtection) { + bgView.visibility = View.VISIBLE + } else { + bgView.visibility = View.GONE + } } } launch { - viewModel.isLongPressEnabled.collect { isEnabled -> - longPressHandlingView.setLongPressHandlingEnabled(isEnabled) + viewModel.burnInOffsets.collect { burnInOffsets -> + view.translationX = burnInOffsets.x.toFloat() + view.translationY = burnInOffsets.y.toFloat() + view.aodFpDrawable.progress = burnInOffsets.progress } } + + launch { viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha } } + } + } + + fgIconView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { launch { - viewModel.accessibilityDelegateHint.collect { hint -> - view.accessibilityHintType = hint + fgViewModel.viewModel.collect { viewModel -> + fgIconView.setImageState( + view.getIconState(viewModel.type, viewModel.useAodVariant), + /* merge */ false + ) + fgIconView.imageTintList = ColorStateList.valueOf(viewModel.tint) + fgIconView.setPadding( + viewModel.padding, + viewModel.padding, + viewModel.padding, + viewModel.padding, + ) + } + } + } + } + + bgView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + bgViewModel.viewModel.collect { bgViewModel -> + bgView.alpha = bgViewModel.alpha + bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt deleted file mode 100644 index 5900a2467994..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.keyguard.ui.binder - -import android.view.View -import android.view.ViewGroup -import android.view.ViewPropertyAnimator -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R -import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel -import com.android.systemui.lifecycle.repeatWhenAttached -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -object KeyguardAmbientIndicationAreaViewBinder { - /** - * Defines interface for an object that acts as the binding between the view and its view-model. - * - * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after - * it is bound. - */ - interface Binding { - /** - * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the - * indication areas. - */ - fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> - - /** Notifies that device configuration has changed. */ - fun onConfigurationChanged() - - /** Destroys this binding, releases resources, and cancels any coroutines. */ - fun destroy() - } - - @OptIn(ExperimentalCoroutinesApi::class) - fun bind( - view: ViewGroup, - viewModel: KeyguardAmbientIndicationViewModel, - keyguardRootViewModel: KeyguardRootViewModel, - ): Binding { - val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container) - val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) - - val disposableHandle = - view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - keyguardRootViewModel.alpha.collect { alpha -> - ambientIndicationArea?.apply { - this.importantForAccessibility = - if (alpha == 0f) { - View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - } else { - View.IMPORTANT_FOR_ACCESSIBILITY_AUTO - } - this.alpha = alpha - } - } - } - - launch { - viewModel.indicationAreaTranslationX.collect { translationX -> - ambientIndicationArea?.translationX = translationX - } - } - - launch { - configurationBasedDimensions - .map { it.defaultBurnInPreventionYOffsetPx } - .flatMapLatest { defaultBurnInOffsetY -> - viewModel.indicationAreaTranslationY(defaultBurnInOffsetY) - } - .collect { translationY -> - ambientIndicationArea?.translationY = translationY - } - } - - } - } - - - return object : Binding { - override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> { - return listOf(ambientIndicationArea).mapNotNull { it?.animate() } - } - - override fun onConfigurationChanged() { - configurationBasedDimensions.value = loadFromResources(view) - } - - override fun destroy() { - disposableHandle.dispose() - } - } - } - - private fun loadFromResources(view: View): ConfigurationBasedDimensions { - return ConfigurationBasedDimensions( - defaultBurnInPreventionYOffsetPx = - view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset), - ) - } - - private data class ConfigurationBasedDimensions( - val defaultBurnInPreventionYOffsetPx: Int, - ) -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 1f74bb661135..4de9fc343af1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -41,7 +41,7 @@ import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.flags.RefactorFlag +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel @@ -116,7 +116,7 @@ object KeyguardRootViewBinder { launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } } } - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { launch { viewModel.burnInLayerVisibility.collect { visibility -> childViews[burnInLayerId]?.visibility = visibility @@ -342,7 +342,7 @@ object KeyguardRootViewBinder { featureFlags: FeatureFlagsClassic, screenOffAnimationController: ScreenOffAnimationController, ): DisposableHandle? { - RefactorFlag(featureFlags, Flags.MIGRATE_KEYGUARD_STATUS_VIEW).assertInLegacyMode() + KeyguardShadeMigrationNssl.assertInLegacyMode() if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return null return view.repeatWhenAttached { lifecycleScope.launch { @@ -368,7 +368,7 @@ object KeyguardRootViewBinder { iconsAppearTranslationPx: Int, screenOffAnimationController: ScreenOffAnimationController, ) { - val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW) + val statusViewMigrated = KeyguardShadeMigrationNssl.isEnabled animate().cancel() val animatorListener = object : AnimatorListenerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt index 9872d97021fa..52d87d369083 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt @@ -42,9 +42,9 @@ object UdfpsAodFingerprintViewBinder { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { viewModel.burnInOffsets.collect { burnInOffsets -> - view.progress = burnInOffsets.burnInProgress - view.translationX = burnInOffsets.burnInXOffset.toFloat() - view.translationY = burnInOffsets.burnInYOffset.toFloat() + view.progress = burnInOffsets.progress + view.translationX = burnInOffsets.x.toFloat() + view.translationY = burnInOffsets.y.toFloat() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt index bab04f234b3f..d4621e6e2356 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt @@ -59,8 +59,8 @@ object UdfpsFingerprintViewBinder { launch { viewModel.burnInOffsets.collect { burnInOffsets -> - view.translationX = burnInOffsets.burnInXOffset.toFloat() - view.translationY = burnInOffsets.burnInYOffset.toFloat() + view.translationX = burnInOffsets.x.toFloat() + view.translationY = burnInOffsets.y.toFloat() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index bdd9a6bf3f79..a2e930c49511 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -49,7 +49,7 @@ import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder @@ -445,7 +445,7 @@ constructor( private fun setUpClock(previewContext: Context, parentView: ViewGroup) { largeClockHostView = - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view_large) } else { val hostView = FrameLayout(previewContext) @@ -460,7 +460,7 @@ constructor( largeClockHostView.isInvisible = true smallClockHostView = - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view) } else { val resources = parentView.resources diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt new file mode 100644 index 000000000000..b58a80ff8d34 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.transitions + +import kotlinx.coroutines.flow.Flow + +/** + * Each DeviceEntryIconTransition is responsible for updating the given parameters for the current + * keyguard transition. + * * + * MUST list implementing classes in dagger module [DeviceEntryIconTransitionModule]. + */ +interface DeviceEntryIconTransition { + val deviceEntryParentViewAlpha: Flow<Float> +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt new file mode 100644 index 000000000000..9d557bb8a3b2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.keyguard.ui.transitions + +import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +@Module +abstract class DeviceEntryIconTransitionModule { + @Binds + @IntoSet + abstract fun aodToLockscreen( + impl: AodToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun dozingToLockscreen( + impl: DozingToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun dreamingToLockscreen( + impl: DreamingToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToAod( + impl: LockscreenToAodTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToDreaming( + impl: LockscreenToDreamingTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToOccluded( + impl: LockscreenToOccludedTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToPrimaryBouncer( + impl: LockscreenToPrimaryBouncerTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToGone( + impl: LockscreenToGoneTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun goneToAod(impl: GoneToAodTransitionViewModel): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun occludedToAod(impl: OccludedToAodTransitionViewModel): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun occludedToLockscreen( + impl: OccludedToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun primaryBouncerToAod( + impl: PrimaryBouncerToAodTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun primaryBouncerToLockscreen( + impl: PrimaryBouncerToLockscreenTransitionViewModel + ): DeviceEntryIconTransition +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt index c9e395402dad..af1d0df92652 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt @@ -40,7 +40,10 @@ constructor( attrs: AttributeSet?, defStyleAttrs: Int = 0, ) : FrameLayout(context, attrs, defStyleAttrs) { - val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs) + val longPressHandlingView: LongPressHandlingView = + LongPressHandlingView(context, attrs) { + context.resources.getInteger(R.integer.config_lockIconLongPress).toLong() + } val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg } val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg } val aodFpDrawable: LottieDrawable = LottieDrawable() @@ -105,7 +108,7 @@ constructor( // FINGERPRINT animatedIconDrawable.addState( getIconState(IconType.FINGERPRINT, false), - context.getDrawable(R.drawable.ic_kg_fingerprint)!!, + context.getDrawable(R.drawable.ic_fingerprint)!!, R.id.locked_fp, ) @@ -220,7 +223,7 @@ constructor( val lp = longPressHandlingView.layoutParams as LayoutParams lp.height = ViewGroup.LayoutParams.MATCH_PARENT lp.width = ViewGroup.LayoutParams.MATCH_PARENT - longPressHandlingView.setLayoutParams(lp) + longPressHandlingView.layoutParams = lp } private fun addIconImageView() { @@ -231,7 +234,7 @@ constructor( lp.height = ViewGroup.LayoutParams.MATCH_PARENT lp.width = ViewGroup.LayoutParams.MATCH_PARENT lp.gravity = Gravity.CENTER - iconView.setLayoutParams(lp) + iconView.layoutParams = lp } private fun addBgImageView() { @@ -240,7 +243,7 @@ constructor( val lp = bgView.layoutParams as LayoutParams lp.height = ViewGroup.LayoutParams.MATCH_PARENT lp.width = ViewGroup.LayoutParams.MATCH_PARENT - bgView.setLayoutParams(lp) + bgView.layoutParams = lp } fun getIconState(icon: IconType, aod: Boolean): IntArray { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index 2e64c41bace8..0cf891c3f665 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -20,10 +20,10 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.items.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection @@ -31,8 +31,13 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopu import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection +import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines +import java.util.Optional import javax.inject.Inject +import javax.inject.Named +import kotlin.jvm.optionals.getOrNull /** * Positions elements of the lockscreen to the default position. @@ -47,7 +52,8 @@ constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, defaultShortcutsSection: DefaultShortcutsSection, - defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, + @Named(KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) + defaultAmbientIndicationAreaSection: Optional<KeyguardSection>, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, defaultStatusViewSection: DefaultStatusViewSection, defaultStatusBarSection: DefaultStatusBarSection, @@ -61,11 +67,11 @@ constructor( override val id: String = DEFAULT override val sections = - listOf( + listOfNotNull( defaultIndicationAreaSection, defaultDeviceEntryIconSection, defaultShortcutsSection, - defaultAmbientIndicationAreaSection, + defaultAmbientIndicationAreaSection.getOrNull(), defaultSettingsPopupMenuSection, defaultStatusViewSection, defaultStatusBarSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index d8b368b4a0d3..14e8f892e101 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -19,18 +19,22 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines +import com.android.systemui.util.kotlin.getOrNull +import java.util.Optional import javax.inject.Inject +import javax.inject.Named /** Vertically aligns the shortcuts with the udfps. */ @SysUISingleton @@ -39,7 +43,8 @@ class ShortcutsBesideUdfpsKeyguardBlueprint constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, - defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, + @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) + defaultAmbientIndicationAreaSection: Optional<KeyguardSection>, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection, defaultStatusViewSection: DefaultStatusViewSection, @@ -52,10 +57,10 @@ constructor( override val id: String = SHORTCUTS_BESIDE_UDFPS override val sections = - listOf( + listOfNotNull( defaultIndicationAreaSection, defaultDeviceEntryIconSection, - defaultAmbientIndicationAreaSection, + defaultAmbientIndicationAreaSection.getOrNull(), defaultSettingsPopupMenuSection, alignShortcutsToUdfpsSection, defaultStatusViewSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt index 35679b84771b..0d397bff72ce 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt @@ -20,18 +20,22 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection +import com.android.systemui.util.kotlin.getOrNull +import java.util.Optional import javax.inject.Inject +import javax.inject.Named /** * Split-shade layout, mostly used for larger devices like foldables and tablets when in landscape @@ -45,7 +49,8 @@ constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, defaultShortcutsSection: DefaultShortcutsSection, - defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, + @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) + defaultAmbientIndicationAreaSection: Optional<KeyguardSection>, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, defaultStatusViewSection: DefaultStatusViewSection, defaultStatusBarSection: DefaultStatusBarSection, @@ -58,11 +63,11 @@ constructor( override val id: String = ID override val sections = - listOf( + listOfNotNull( defaultIndicationAreaSection, defaultDeviceEntryIconSection, defaultShortcutsSection, - defaultAmbientIndicationAreaSection, + defaultAmbientIndicationAreaSection.getOrNull(), defaultSettingsPopupMenuSection, defaultStatusViewSection, defaultStatusBarSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt index eb01d4f6f61c..b7a165c212fd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt @@ -28,6 +28,8 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel @@ -46,6 +48,7 @@ constructor( private val falsingManager: FalsingManager, private val indicationController: KeyguardIndicationController, private val vibratorHelper: VibratorHelper, + private val featureFlags: FeatureFlagsClassic, ) : BaseShortcutSection() { override fun addViews(constraintLayout: ConstraintLayout) { if (keyguardBottomAreaRefactor()) { @@ -83,20 +86,26 @@ constructor( val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width) val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height) + val lockIconViewId = if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + R.id.device_entry_icon_view + } else { + R.id.lock_icon_view + } + constraintSet.apply { constrainWidth(R.id.start_button, width) constrainHeight(R.id.start_button, height) connect(R.id.start_button, LEFT, PARENT_ID, LEFT) - connect(R.id.start_button, RIGHT, R.id.lock_icon_view, LEFT) - connect(R.id.start_button, TOP, R.id.lock_icon_view, TOP) - connect(R.id.start_button, BOTTOM, R.id.lock_icon_view, BOTTOM) + connect(R.id.start_button, RIGHT, lockIconViewId, LEFT) + connect(R.id.start_button, TOP, lockIconViewId, TOP) + connect(R.id.start_button, BOTTOM, lockIconViewId, BOTTOM) constrainWidth(R.id.end_button, width) constrainHeight(R.id.end_button, height) connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT) - connect(R.id.end_button, LEFT, R.id.lock_icon_view, RIGHT) - connect(R.id.end_button, TOP, R.id.lock_icon_view, TOP) - connect(R.id.end_button, BOTTOM, R.id.lock_icon_view, BOTTOM) + connect(R.id.end_button, LEFT, lockIconViewId, RIGHT) + connect(R.id.end_button, TOP, lockIconViewId, TOP) + connect(R.id.end_button, BOTTOM, lockIconViewId, BOTTOM) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index 09caf4505c3b..484d351a362e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -22,8 +22,7 @@ import android.view.View import androidx.constraintlayout.helper.widget.Layer import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import javax.inject.Inject @@ -33,11 +32,10 @@ class AodBurnInSection @Inject constructor( private val context: Context, - private val featureFlags: FeatureFlags, ) : KeyguardSection() { override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } @@ -53,13 +51,13 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index 975d62a0b9e3..c438e4956923 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -29,14 +29,15 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor -import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.policy.ConfigurationController @@ -49,8 +50,8 @@ constructor( private val context: Context, private val configurationState: ConfigurationState, private val configurationController: ConfigurationController, - private val dozeParameters: DozeParameters, private val featureFlags: FeatureFlagsClassic, + private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore, private val notificationIconAreaController: NotificationIconAreaController, @@ -62,7 +63,7 @@ constructor( private lateinit var nic: NotificationIconContainer override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } nic = @@ -81,7 +82,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } @@ -94,6 +95,7 @@ constructor( nicAodViewModel, configurationState, configurationController, + iconBindingFailureTracker, nicAodIconViewStore, ) } else { @@ -102,7 +104,7 @@ constructor( } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } val bottomMargin = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt deleted file mode 100644 index 20cb9b0576db..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.keyguard.ui.view.layout.sections - -import android.view.LayoutInflater -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM -import androidx.constraintlayout.widget.ConstraintSet.END -import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT -import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID -import androidx.constraintlayout.widget.ConstraintSet.START -import androidx.constraintlayout.widget.ConstraintSet.TOP -import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.Flags.keyguardBottomAreaRefactor -import com.android.systemui.res.R -import com.android.systemui.keyguard.shared.model.KeyguardSection -import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder -import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel -import javax.inject.Inject - -class DefaultAmbientIndicationAreaSection -@Inject -constructor( - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel, - private val keyguardRootViewModel: KeyguardRootViewModel, -) : KeyguardSection() { - private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null - - override fun addViews(constraintLayout: ConstraintLayout) { - if (keyguardBottomAreaRefactor()) { - val view = - LayoutInflater.from(constraintLayout.context) - .inflate(R.layout.ambient_indication, constraintLayout, false) - - constraintLayout.addView(view) - } - } - - override fun bindData(constraintLayout: ConstraintLayout) { - if (keyguardBottomAreaRefactor()) { - ambientIndicationAreaHandle = - KeyguardAmbientIndicationAreaViewBinder.bind( - constraintLayout, - keyguardAmbientIndicationViewModel, - keyguardRootViewModel, - ) - } - } - - override fun applyConstraints(constraintSet: ConstraintSet) { - constraintSet.apply { - constrainWidth(R.id.ambient_indication_container, MATCH_PARENT) - - if (keyguardUpdateMonitor.isUdfpsSupported) { - // constrain below udfps and above indication area - constrainHeight(R.id.ambient_indication_container, MATCH_CONSTRAINT) - connect(R.id.ambient_indication_container, TOP, R.id.lock_icon_view, BOTTOM) - connect( - R.id.ambient_indication_container, - BOTTOM, - R.id.keyguard_indication_area, - TOP - ) - connect(R.id.ambient_indication_container, START, PARENT_ID, START) - connect(R.id.ambient_indication_container, END, PARENT_ID, END) - } else { - // constrain above lock icon - constrainHeight(R.id.ambient_indication_container, WRAP_CONTENT) - connect(R.id.ambient_indication_container, BOTTOM, R.id.lock_icon_view, TOP) - connect(R.id.ambient_indication_container, START, PARENT_ID, START) - connect(R.id.ambient_indication_container, END, PARENT_ID, END) - } - } - } - - override fun removeViews(constraintLayout: ConstraintLayout) { - ambientIndicationAreaHandle?.destroy() - - constraintLayout.removeView(R.id.ambient_indication_container) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt index ace970a01054..13ea8ff8e388 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt @@ -36,6 +36,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R @@ -56,12 +58,15 @@ constructor( private val featureFlags: FeatureFlags, private val lockIconViewController: Lazy<LockIconViewController>, private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>, + private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>, + private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, private val falsingManager: Lazy<FalsingManager>, ) : KeyguardSection() { private val deviceEntryIconViewId = R.id.device_entry_icon_view override fun addViews(constraintLayout: ConstraintLayout) { - if (!keyguardBottomAreaRefactor() && + if ( + !keyguardBottomAreaRefactor() && !featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS) ) { return @@ -87,6 +92,8 @@ constructor( DeviceEntryIconViewBinder.bind( it, deviceEntryIconViewModel.get(), + deviceEntryForegroundViewModel.get(), + deviceEntryBackgroundViewModel.get(), falsingManager.get(), ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index c2aedca4ffbc..165ee364c2c8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -26,6 +26,7 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -40,7 +41,7 @@ class DefaultNotificationStackScrollLayoutSection @Inject constructor( context: Context, - featureFlags: FeatureFlags, + private val featureFlags: FeatureFlags, notificationPanelView: NotificationPanelView, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, @@ -50,7 +51,6 @@ constructor( ) : NotificationStackScrollLayoutSection( context, - featureFlags, notificationPanelView, sharedNotificationContainer, sharedNotificationContainerViewModel, @@ -58,7 +58,7 @@ constructor( notificationStackSizeCalculator, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } constraintSet.apply { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt index 0b0f21d7a281..4abcca9d1151 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt @@ -31,9 +31,8 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.keyguard.KeyguardStatusView import com.android.keyguard.dagger.KeyguardStatusViewComponent -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardViewConfigurator +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.media.controls.ui.KeyguardMediaController import com.android.systemui.res.R @@ -49,7 +48,6 @@ class DefaultStatusViewSection @Inject constructor( private val context: Context, - private val featureFlags: FeatureFlags, private val notificationPanelView: NotificationPanelView, private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>, @@ -60,7 +58,7 @@ constructor( private val statusViewId = R.id.keyguard_status_view override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available. @@ -82,7 +80,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { constraintLayout.findViewById<KeyguardStatusView?>(R.id.keyguard_status_view)?.let { val statusViewComponent = keyguardStatusViewComponentFactory.build(it, context.display) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt new file mode 100644 index 000000000000..37c00b61c4dd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout.sections + +import com.android.systemui.keyguard.shared.model.KeyguardSection +import dagger.BindsOptionalOf +import dagger.Module +import javax.inject.Named + +@Module +abstract class KeyguardSectionsModule { + + companion object { + const val KEYGUARD_AMBIENT_INDICATION_AREA_SECTION = + "keyguard_ambient_indication_area_section" + } + + @BindsOptionalOf + @Named(KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) + abstract fun defaultAmbientIndicationAreaSection(): KeyguardSection + +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index ea2bdf79a114..441f59d6df1d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -21,8 +21,7 @@ import android.content.Context import android.view.View import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -36,7 +35,6 @@ import kotlinx.coroutines.DisposableHandle abstract class NotificationStackScrollLayoutSection constructor( protected val context: Context, - protected val featureFlags: FeatureFlags, private val notificationPanelView: NotificationPanelView, private val sharedNotificationContainer: SharedNotificationContainer, private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, @@ -47,7 +45,7 @@ constructor( private var disposableHandle: DisposableHandle? = null override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } // This moves the existing NSSL view to a different parent, as the controller is a @@ -62,7 +60,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } disposableHandle?.dispose() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt index dc2ad8d8f718..2c45da63edb4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -26,6 +26,7 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -40,7 +41,7 @@ class SplitShadeNotificationStackScrollLayoutSection @Inject constructor( context: Context, - featureFlags: FeatureFlags, + private val featureFlags: FeatureFlags, notificationPanelView: NotificationPanelView, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, @@ -50,7 +51,6 @@ constructor( ) : NotificationStackScrollLayoutSection( context, - featureFlags, notificationPanelView, sharedNotificationContainer, sharedNotificationContainerViewModel, @@ -58,7 +58,7 @@ constructor( notificationStackSizeCalculator, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } constraintSet.apply { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt new file mode 100644 index 000000000000..4d2af0c7bd4d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** Breaks down AOD->GONE transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class AodToGoneTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromAodTransitionInteractor.TO_GONE_DURATION, + transitionFlow = interactor.transition(KeyguardState.AOD, KeyguardState.GONE), + ) + + override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt index 024707ad2885..14de01b41867 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt @@ -17,22 +17,29 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest /** * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class AodToLockscreenTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( @@ -47,4 +54,21 @@ constructor( onStart = { 1f }, onStep = { 1f }, ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + // fade in + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ) + } else { + // background view isn't visible, so return an empty flow + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt new file mode 100644 index 000000000000..06661d0a466b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject + +/** Breaks down AOD->OCCLUDED transition into discrete steps for corresponding views to consume. */ +@SysUISingleton +class AodToOccludedTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION, + transitionFlow = interactor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED), + ) + + override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt new file mode 100644 index 000000000000..3e8bbb3b4c34 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.Context +import com.android.settingslib.Utils +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart + +/** Models the UI state for the device entry icon background view. */ +@ExperimentalCoroutinesApi +class DeviceEntryBackgroundViewModel +@Inject +constructor( + val context: Context, + configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor + lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel, + aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + goneToAodTransitionViewModel: GoneToAodTransitionViewModel, + primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel, + occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel, + occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, + dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, +) { + private val color: Flow<Int> = + configurationRepository.onAnyConfigurationChange + .map { + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface) + } + .onStart { + emit( + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.colorSurface + ) + ) + } + private val alpha: Flow<Float> = + setOf( + lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, + goneToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + primaryBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, + dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, + ) + .merge() + + val viewModel: Flow<BackgroundViewModel> = + combine(color, alpha) { color, alpha -> + BackgroundViewModel( + alpha = alpha, + tint = color, + ) + } + + data class BackgroundViewModel( + val alpha: Float, + val tint: Int, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt new file mode 100644 index 000000000000..99529a100b07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.Context +import com.android.settingslib.Utils +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.res.R +import javax.inject.Inject +import kotlin.math.roundToInt +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** Models the UI state for the device entry icon foreground view (displayed icon). */ +@ExperimentalCoroutinesApi +class DeviceEntryForegroundViewModel +@Inject +constructor( + val context: Context, + configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + transitionInteractor: KeyguardTransitionInteractor, + deviceEntryIconViewModel: DeviceEntryIconViewModel, +) { + private val isShowingAod: Flow<Boolean> = + transitionInteractor.startedKeyguardState.map { keyguardState -> + keyguardState == KeyguardState.AOD + } + private val color: Flow<Int> = + configurationRepository.onAnyConfigurationChange + .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) } + .onStart { + emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)) + } + private val useAodIconVariant: Flow<Boolean> = + combine(isShowingAod, deviceEntryUdfpsInteractor.isUdfpsSupported) { + isTransitionToAod, + isUdfps -> + isTransitionToAod && isUdfps + } + .distinctUntilChanged() + private val padding: Flow<Int> = + configurationRepository.scaleForResolution.map { scale -> + (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) + .roundToInt() + } + + val viewModel: Flow<ForegroundIconViewModel> = + combine( + deviceEntryIconViewModel.iconType, + useAodIconVariant, + color, + padding, + ) { iconType, useAodVariant, color, padding -> + ForegroundIconViewModel( + type = iconType, + useAodVariant = useAodVariant, + tint = color, + padding = padding, + ) + } + + data class ForegroundIconViewModel( + val type: DeviceEntryIconView.IconType, + val useAodVariant: Boolean, + val tint: Int, + val padding: Int, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index 842dde352c71..5b5a10380a5b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -12,57 +12,202 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package com.android.systemui.keyguard.ui.viewmodel -import android.graphics.Color +import android.animation.FloatEvaluator +import android.animation.IntEvaluator +import com.android.keyguard.KeyguardViewController +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart +/** Models the UI state for the containing device entry icon & long-press handling view. */ @ExperimentalCoroutinesApi -class DeviceEntryIconViewModel @Inject constructor() { - // TODO: b/305234447 update these states from the data layer - val iconViewModel: Flow<IconViewModel> = - flowOf( - IconViewModel( - type = DeviceEntryIconView.IconType.LOCK, - useAodVariant = false, - tint = Color.WHITE, - alpha = 1f, - padding = 48, +class DeviceEntryIconViewModel +@Inject +constructor( + transitions: Set<@JvmSuppressWildcards DeviceEntryIconTransition>, + burnInInteractor: BurnInInteractor, + shadeInteractor: ShadeInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + transitionInteractor: KeyguardTransitionInteractor, + val keyguardInteractor: KeyguardInteractor, + val viewModel: AodToLockscreenTransitionViewModel, + val shadeDependentFlows: ShadeDependentFlows, + private val sceneContainerFlags: SceneContainerFlags, + private val keyguardViewController: Lazy<KeyguardViewController>, + private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, + udfpsInteractor: DeviceEntryUdfpsInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, +) { + private val intEvaluator = IntEvaluator() + private val floatEvaluator = FloatEvaluator() + private val toAodFromState: Flow<KeyguardState> = + transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.from } + private val showingAlternateBouncer: Flow<Boolean> = + transitionInteractor.startedKeyguardState.map { keyguardState -> + keyguardState == KeyguardState.ALTERNATE_BOUNCER + } + private val qsProgress: Flow<Float> = shadeInteractor.qsExpansion.onStart { emit(0f) } + private val shadeExpansion: Flow<Float> = shadeInteractor.shadeExpansion.onStart { emit(0f) } + private val transitionAlpha: Flow<Float> = + transitions.map { it.deviceEntryParentViewAlpha }.merge() + private val alphaMultiplierFromShadeExpansion: Flow<Float> = + combine( + showingAlternateBouncer, + shadeExpansion, + qsProgress, + ) { showingAltBouncer, shadeExpansion, qsProgress -> + val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f) + if (showingAltBouncer) { + 1f + } else { + (1f - shadeExpansion) * (1f - interpolatedQsProgress) + } + } + // Burn-in offsets in AOD + private val nonAnimatedBurnInOffsets: Flow<BurnInOffsets> = + combine( + burnInInteractor.deviceEntryIconXOffset, + burnInInteractor.deviceEntryIconYOffset, + burnInInteractor.udfpsProgress + ) { fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress -> + BurnInOffsets( + fullyDozingBurnInX, + fullyDozingBurnInY, + fullyDozingBurnInProgress, + ) + } + // Burn-in offsets that animate based on the transition amount to AOD + private val animatedBurnInOffsets: Flow<BurnInOffsets> = + combine( + nonAnimatedBurnInOffsets, + transitionInteractor.transitionStepsToState(KeyguardState.AOD) + ) { burnInOffsets, transitionStepsToAod -> + val dozeAmount = transitionStepsToAod.value + BurnInOffsets( + intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.x), + intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.y), + floatEvaluator.evaluate(dozeAmount, 0, burnInOffsets.progress) ) - ) - val backgroundViewModel: Flow<BackgroundViewModel> = - flowOf(BackgroundViewModel(alpha = 1f, tint = Color.GRAY)) - val burnInViewModel: Flow<BurnInViewModel> = flowOf(BurnInViewModel(0, 0, 0f)) - val isLongPressEnabled: Flow<Boolean> = flowOf(true) + } + + val deviceEntryViewAlpha: Flow<Float> = + combine( + transitionAlpha, + alphaMultiplierFromShadeExpansion, + ) { alpha, alphaMultiplier -> + alpha * alphaMultiplier + } + val useBackgroundProtection: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported + val burnInOffsets: Flow<BurnInOffsets> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled -> + if (udfpsEnrolled) { + toAodFromState.flatMapLatest { fromState -> + when (fromState) { + KeyguardState.AOD, + KeyguardState.GONE, + KeyguardState.OCCLUDED, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + KeyguardState.OFF, + KeyguardState.DOZING, + KeyguardState.DREAMING, + KeyguardState.PRIMARY_BOUNCER -> nonAnimatedBurnInOffsets + KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets + KeyguardState.LOCKSCREEN -> + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = nonAnimatedBurnInOffsets, + flowWhenShadeIsNotExpanded = animatedBurnInOffsets, + ) + } + } + } else { + // If UDFPS isn't enrolled, we don't show any UI on AOD so there's no need + // to use burn in offsets at all + flowOf(BurnInOffsets(x = 0, y = 0, progress = 0f)) + } + } + val iconType: Flow<DeviceEntryIconView.IconType> = + combine( + udfpsInteractor.isListeningForUdfps, + deviceEntryInteractor.isUnlocked, + ) { isListeningForUdfps, isUnlocked -> + if (isUnlocked) { + DeviceEntryIconView.IconType.UNLOCK + } else { + if (isListeningForUdfps) { + DeviceEntryIconView.IconType.FINGERPRINT + } else { + DeviceEntryIconView.IconType.LOCK + } + } + } + val isLongPressEnabled: Flow<Boolean> = + combine( + iconType, + deviceEntryUdfpsInteractor.isUdfpsSupported, + ) { deviceEntryStatus, isUdfps -> + when (deviceEntryStatus) { + DeviceEntryIconView.IconType.LOCK -> isUdfps + DeviceEntryIconView.IconType.UNLOCK -> true + DeviceEntryIconView.IconType.FINGERPRINT -> false + } + } val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> = - flowOf(DeviceEntryIconView.AccessibilityHintType.NONE) + combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled -> + if (longPressEnabled) { + deviceEntryStatus.toAccessibilityHintType() + } else { + DeviceEntryIconView.AccessibilityHintType.NONE + } + } fun onLongPress() { - // TODO() vibrate & perform action based on current lock/unlock state - } - data class BurnInViewModel( - val x: Int, // current x burn in offset based on the aodTransitionAmount - val y: Int, // current y burn in offset based on the aodTransitionAmount - val progress: Float, // current progress based on the aodTransitionAmount - ) + deviceEntryHapticsInteractor.vibrateSuccess() - class IconViewModel( - val type: DeviceEntryIconView.IconType, - val useAodVariant: Boolean, - val tint: Int, - val alpha: Float, - val padding: Int, - ) + // TODO (b/309804148): play auth ripple via an interactor - class BackgroundViewModel( - val alpha: Float, - val tint: Int, - ) + if (sceneContainerFlags.isEnabled()) { + deviceEntryInteractor.attemptDeviceEntry() + } else { + keyguardViewController.get().showPrimaryBouncer(/* scrim */ true) + } + } + + private fun DeviceEntryIconView.IconType.toAccessibilityHintType(): + DeviceEntryIconView.AccessibilityHintType { + return when (this) { + DeviceEntryIconView.IconType.LOCK -> + DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE + DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER + DeviceEntryIconView.IconType.FINGERPRINT -> + DeviceEntryIconView.AccessibilityHintType.NONE + } + } } + +data class BurnInOffsets( + val x: Int, // current x burn in offset based on the aodTransitionAmount + val y: Int, // current y burn in offset based on the aodTransitionAmount + val progress: Float, // current progress based on the aodTransitionAmount +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..27fb8a3d2473 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down DOZING->LOCKSCREEN transition into discrete steps for corresponding views to consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DozingToLockscreenTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation: KeyguardTransitionAnimationFlow = + KeyguardTransitionAnimationFlow( + transitionDuration = FromDozingTransitionInteractor.TO_LOCKSCREEN_DURATION, + transitionFlow = interactor.dozingToLockscreenTransition, + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(1f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index e24d326850e0..a3b8b85fc53d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -18,27 +18,34 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest /** * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to * consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class DreamingToLockscreenTransitionViewModel @Inject constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, - private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor -) { + private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor, + private val deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { fun startTransition() = fromDreamingTransitionInteractor.startToLockscreenTransition() private val transitionAnimation = @@ -88,4 +95,15 @@ constructor( duration = 250.milliseconds, onStep = { it }, ) + + val deviceEntryBackgroundViewAlpha = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + // immediately show; will fade in with deviceEntryParentViewAlpha + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } + override val deviceEntryParentViewAlpha = lockscreenAlpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt index 601dbccb1de1..62b2281ae473 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt @@ -18,20 +18,27 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest /** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class GoneToAodTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( @@ -60,4 +67,21 @@ constructor( onStart = { 0f }, onStep = { it }, ) + val deviceEntryBackgroundViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled -> + if (udfpsEnrolled) { + // fade in at the end of the transition to give time for FP to start running + // and avoid a flicker of the unlocked icon + transitionAnimation.createFlow( + startTime = 1100.milliseconds, + duration = 200.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ) + } else { + emptyFlow() + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt deleted file mode 100644 index dd3967aed99d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.keyguard.ui.viewmodel - -import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map -import javax.inject.Inject - -class KeyguardAmbientIndicationViewModel -@Inject -constructor( - private val keyguardInteractor: KeyguardInteractor, - private val burnInHelperWrapper: BurnInHelperWrapper, -) { - - /** An observable for the x-offset by which the indication area should be translated. */ - val indicationAreaTranslationX: Flow<Float> = - keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() - - /** Returns an observable for the y-offset by which the indication area should be translated. */ - fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> { - return keyguardInteractor.dozeAmount - .map { dozeAmount -> - dozeAmount * - (burnInHelperWrapper.burnInOffset( - /* amplitude = */ defaultBurnInOffset * 2, - /* xAxis= */ false, - ) - defaultBurnInOffset) - } - .distinctUntilChanged() - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt new file mode 100644 index 000000000000..2bf12e8e33b2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest + +/** + * Breaks down LOCKSCREEN->AOD transition into discrete steps for corresponding views to consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class LockscreenToAodTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromLockscreenTransitionInteractor.TO_AOD_DURATION, + transitionFlow = interactor.lockscreenToAodTransition, + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + flowWhenShadeIsNotExpanded = + transitionAnimation.createFlow( + duration = 300.milliseconds, + onStep = { 1 - it }, + onFinish = { 0f }, + ), + ) + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { + isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = // fade in + transitionAnimation.createFlow( + duration = 300.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ), + flowWhenShadeIsNotExpanded = transitionAnimation.immediatelyTransitionTo(1f), + ) + } else { + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + flowWhenShadeIsNotExpanded = // fade out + transitionAnimation.createFlow( + duration = 200.milliseconds, + onStep = { 1f - it }, + onFinish = { 0f }, + ), + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt index a3ae67d906bd..52296137a3d6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -34,7 +35,8 @@ class LockscreenToDreamingTransitionViewModel @Inject constructor( interactor: KeyguardTransitionInteractor, -) { + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( transitionDuration = TO_DREAMING_DURATION, @@ -60,6 +62,12 @@ constructor( onStep = { 1f - it }, ) + override val deviceEntryParentViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsNotExpanded = lockscreenAlpha, + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + ) + companion object { @JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt new file mode 100644 index 000000000000..59e5aa845051 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down LOCKSCREEN->GONE transition into discrete steps for corresponding views to consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class LockscreenToGoneTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromLockscreenTransitionInteractor.TO_GONE_DURATION, + transitionFlow = interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.GONE), + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt index d3ea89ce1935..d49bc4994b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -33,8 +34,9 @@ import kotlinx.coroutines.flow.Flow class LockscreenToOccludedTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( transitionDuration = TO_OCCLUDED_DURATION, @@ -59,4 +61,10 @@ constructor( interpolator = EMPHASIZED_ACCELERATE, ) } + + override val deviceEntryParentViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsNotExpanded = lockscreenAlpha, + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt new file mode 100644 index 000000000000..f04b67a1d4d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down LOCKSCREEN->PRIMARY BOUNCER transition into discrete steps for corresponding views to + * consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class LockscreenToPrimaryBouncerTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + transitionFlow = + interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER), + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsNotExpanded = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1f - it }, + onFinish = { 0f } + ), + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f) + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt new file mode 100644 index 000000000000..f7cff9b28542 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest + +/** Breaks down OCCLUDED->AOD transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class OccludedToAodTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromOccludedTransitionInteractor.TO_AOD_DURATION, + transitionFlow = interactor.transition(KeyguardState.OCCLUDED, KeyguardState.AOD), + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) + + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled + -> + if (udfpsEnrolledAndEnabled) { + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index 6845c55b8385..0bdc85d05106 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -18,23 +18,30 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest /** * Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to * consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class OccludedToLockscreenTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( transitionDuration = TO_LOCKSCREEN_DURATION, @@ -58,4 +65,16 @@ constructor( duration = 250.milliseconds, onStep = { it }, ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { + isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt new file mode 100644 index 000000000000..05a6d5810be3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest + +/** + * Breaks down PRIMARY BOUNCER->AOD transition into discrete steps for corresponding views to + * consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class PrimaryBouncerToAodTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, + transitionFlow = + interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD), + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + transitionAnimation.immediatelyTransitionTo(0f) + } else { + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { + isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + transitionAnimation.createFlow( + duration = 300.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ) + } else { + emptyFlow() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..3cf793ab9dc8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest + +/** + * Breaks down PRIMARY BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views to + * consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class PrimaryBouncerToLockscreenTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, + transitionFlow = + interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.LOCKSCREEN), + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(1f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt new file mode 100644 index 000000000000..e45d537155fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** Helper for flows that depend on the shade expansion */ +class ShadeDependentFlows +@Inject +constructor( + transitionInteractor: KeyguardTransitionInteractor, + shadeInteractor: ShadeInteractor, +) { + /** When the last keyguard state transition started, was the shade fully expanded? */ + private val lastStartedTransitionHadShadeFullyExpanded: Flow<Boolean> = + transitionInteractor.startedKeyguardState.sample(shadeInteractor.isAnyFullyExpanded) + + /** + * Decide which flow to use depending on the shade expansion state at the start of the last + * keyguard state transition. + */ + fun <T> transitionFlow( + flowWhenShadeIsExpanded: Flow<T>, + flowWhenShadeIsNotExpanded: Flow<T>, + ): Flow<T> { + val filteredFlowWhenShadeIsExpanded = + flowWhenShadeIsExpanded + .sample(lastStartedTransitionHadShadeFullyExpanded, ::Pair) + .filter { (_, shadeFullyExpanded) -> shadeFullyExpanded } + .map { (valueWhenShadeIsExpanded, _) -> valueWhenShadeIsExpanded } + val filteredFlowWhenShadeIsNotExpanded = + flowWhenShadeIsNotExpanded + .sample(lastStartedTransitionHadShadeFullyExpanded, ::Pair) + .filter { (_, shadeFullyExpanded) -> !shadeFullyExpanded } + .map { (valueWhenShadeIsNotExpanded, _) -> valueWhenShadeIsNotExpanded } + return merge(filteredFlowWhenShadeIsExpanded, filteredFlowWhenShadeIsNotExpanded) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt index c10a4635644f..6e77e13e8513 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt @@ -17,9 +17,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context -import com.android.systemui.res.R -import com.android.systemui.keyguard.domain.interactor.BurnInOffsets +import com.android.systemui.keyguard.domain.interactor.Offsets import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.res.R import javax.inject.Inject import kotlin.math.roundToInt import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -35,7 +35,7 @@ constructor( val context: Context, ) { val alpha: Flow<Float> = interactor.dozeAmount - val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets val isVisible: Flow<Boolean> = alpha.map { it != 0f } // Padding between the fingerprint icon and its bounding box in pixels. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt index 0b1079f69d6e..642904df21b7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt @@ -19,9 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context import androidx.annotation.ColorInt import com.android.settingslib.Utils.getColorAttrDefaultColor -import com.android.systemui.keyguard.domain.interactor.BurnInOffsets import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.Offsets import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState @@ -185,7 +185,7 @@ constructor( keyguardInteractor, ) { val dozeAmount: Flow<Float> = interactor.dozeAmount - val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets // Padding between the fingerprint icon and its bounding box in pixels. val padding: Flow<Int> = diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt index 8453af19d26f..0f54e934f3cf 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt @@ -80,12 +80,13 @@ class MediaProjectionPermissionDialogDelegate( R.string.media_projection_entry_app_permission_dialog_warning_entire_screen } + val singleAppOptionDisabled = + appName != null && + mediaProjectionConfig?.regionToCapture == + MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY + val singleAppDisabledText = - if ( - appName != null && - mediaProjectionConfig?.regionToCapture == - MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY - ) { + if (singleAppOptionDisabled) { context.getString( R.string.media_projection_entry_app_permission_dialog_single_app_disabled, appName @@ -93,19 +94,26 @@ class MediaProjectionPermissionDialogDelegate( } else { null } - return listOf( - ScreenShareOption( - mode = SINGLE_APP, - spinnerText = R.string.screen_share_permission_dialog_option_single_app, - warningText = singleAppWarningText, - spinnerDisabledText = singleAppDisabledText, - ), - ScreenShareOption( - mode = ENTIRE_SCREEN, - spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, - warningText = entireScreenWarningText + val options = + listOf( + ScreenShareOption( + mode = SINGLE_APP, + spinnerText = R.string.screen_share_permission_dialog_option_single_app, + warningText = singleAppWarningText, + spinnerDisabledText = singleAppDisabledText, + ), + ScreenShareOption( + mode = ENTIRE_SCREEN, + spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, + warningText = entireScreenWarningText + ) ) - ) + return if (singleAppOptionDisabled) { + // Make sure "Entire screen" is the first option when "Single App" is disabled. + options.reversed() + } else { + options + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 79aedffc4498..9f5e1b79765e 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -35,6 +35,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.Display; import android.view.IWindowManager; import android.view.View; @@ -94,6 +95,9 @@ public class NavigationBarControllerImpl implements @VisibleForTesting SparseArray<NavigationBar> mNavigationBars = new SparseArray<>(); + /** Local cache for {@link IWindowManager#hasNavigationBar(int)}. */ + private SparseBooleanArray mHasNavBar = new SparseBooleanArray(); + // Tracks config changes that will actually recreate the nav bar private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE @@ -221,10 +225,16 @@ public class NavigationBarControllerImpl implements } private boolean shouldCreateNavBarAndTaskBar(int displayId) { + if (mHasNavBar.indexOfKey(displayId) > -1) { + return mHasNavBar.get(displayId); + } + final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); try { - return wms.hasNavigationBar(displayId); + boolean hasNavigationBar = wms.hasNavigationBar(displayId); + mHasNavBar.put(displayId, hasNavigationBar); + return hasNavigationBar; } catch (RemoteException e) { // Cannot get wms, just return false with warning message. Log.w(TAG, "Cannot get WindowManager."); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt index 12a083e990f8..5e19439cd643 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt @@ -116,7 +116,7 @@ class QSTileViewModelImpl<DATA_TYPE>( ) override fun forceUpdate() { - forceUpdates.tryEmit(Unit) + tileScope.launch { forceUpdates.emit(Unit) } } override fun onUserChanged(user: UserHandle) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt index 7b4b55702f55..5bdb592a3558 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt @@ -20,9 +20,12 @@ import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View +import android.view.View.AccessibilityDelegate import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup +import android.view.accessibility.AccessibilityNodeInfo +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import android.widget.ImageView import android.widget.Switch import android.widget.TextView @@ -32,13 +35,18 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.time.SystemClock +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.isActive +import kotlinx.coroutines.withContext /** Dialog for showing active, connected and saved bluetooth devices. */ @SysUISingleton @@ -47,6 +55,7 @@ constructor( private val bluetoothToggleInitialValue: Boolean, private val subtitleResIdInitialValue: Int, private val bluetoothTileDialogCallback: BluetoothTileDialogCallback, + @Main private val mainDispatcher: CoroutineDispatcher, private val systemClock: SystemClock, private val uiEventLogger: UiEventLogger, private val logger: BluetoothTileDialogLogger, @@ -65,13 +74,17 @@ constructor( private val deviceItemAdapter: Adapter = Adapter(bluetoothTileDialogCallback) + private var lastUiUpdateMs: Long = -1 + + private var lastItemRow: Int = -1 + private lateinit var toggleView: Switch private lateinit var subtitleTextView: TextView private lateinit var doneButton: View private lateinit var seeAllViewGroup: View private lateinit var pairNewDeviceViewGroup: View - private lateinit var seeAllText: View - private lateinit var pairNewDeviceText: View + private lateinit var seeAllRow: View + private lateinit var pairNewDeviceRow: View private lateinit var deviceListView: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { @@ -88,8 +101,8 @@ constructor( doneButton = requireViewById(R.id.done_button) seeAllViewGroup = requireViewById(R.id.see_all_layout_group) pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group) - seeAllText = requireViewById(R.id.see_all_text) - pairNewDeviceText = requireViewById(R.id.pair_new_device_text) + seeAllRow = requireViewById(R.id.see_all_clickable_row) + pairNewDeviceRow = requireViewById(R.id.pair_new_device_clickable_row) deviceListView = requireViewById<RecyclerView>(R.id.device_list) setupToggle() @@ -97,22 +110,37 @@ constructor( subtitleTextView.text = context.getString(subtitleResIdInitialValue) doneButton.setOnClickListener { dismiss() } - seeAllText.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) } - pairNewDeviceText.setOnClickListener { + seeAllRow.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) } + pairNewDeviceRow.setOnClickListener { bluetoothTileDialogCallback.onPairNewDeviceClicked(it) } } - internal fun onDeviceItemUpdated( + override fun start() { + lastUiUpdateMs = systemClock.elapsedRealtime() + } + + internal suspend fun onDeviceItemUpdated( deviceItem: List<DeviceItem>, showSeeAll: Boolean, showPairNewDevice: Boolean ) { - val start = systemClock.elapsedRealtime() - deviceItemAdapter.refreshDeviceItemList(deviceItem) { - seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE - pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE - logger.logDeviceUiUpdate(systemClock.elapsedRealtime() - start) + withContext(mainDispatcher) { + val start = systemClock.elapsedRealtime() + val itemRow = deviceItem.size + showSeeAll.toInt() + showPairNewDevice.toInt() + // Add a slight delay for smoother dialog height change + if (itemRow != lastItemRow) { + delay(MIN_HEIGHT_CHANGE_INTERVAL_MS - (start - lastUiUpdateMs)) + } + if (isActive) { + deviceItemAdapter.refreshDeviceItemList(deviceItem) { + seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE + pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE + lastUiUpdateMs = systemClock.elapsedRealtime() + lastItemRow = itemRow + logger.logDeviceUiUpdate(lastUiUpdateMs - start) + } + } } } @@ -169,7 +197,8 @@ constructor( deviceItem1.iconWithDescription?.second == deviceItem2.iconWithDescription?.second && deviceItem1.background == deviceItem2.background && - deviceItem1.isEnabled == deviceItem2.isEnabled + deviceItem1.isEnabled == deviceItem2.isEnabled && + deviceItem1.actionAccessibilityLabel == deviceItem2.actionAccessibilityLabel } } @@ -213,6 +242,21 @@ constructor( mutableDeviceItemClick.tryEmit(item) uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED) } + accessibilityDelegate = + object : AccessibilityDelegate() { + override fun onInitializeAccessibilityNodeInfo( + host: View, + info: AccessibilityNodeInfo + ) { + super.onInitializeAccessibilityNodeInfo(host, info) + info.addAction( + AccessibilityAction( + AccessibilityAction.ACTION_CLICK.id, + item.actionAccessibilityLabel + ) + ) + } + } } nameView.text = item.deviceName summaryView.text = item.connectionSummary @@ -230,6 +274,7 @@ constructor( } internal companion object { + const val MIN_HEIGHT_CHANGE_INTERVAL_MS = 800L const val MAX_DEVICE_ITEM_ENTRY = 3 const val ACTION_BLUETOOTH_DEVICE_DETAILS = "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS" @@ -238,5 +283,9 @@ constructor( const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS" const val DISABLED_ALPHA = 0.3f const val ENABLED_ALPHA = 1f + + private fun Boolean.toInt(): Int { + return if (this) 1 else 0 + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt index f7e0de3ed883..34c2aba1a71f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt @@ -40,7 +40,6 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -76,6 +75,7 @@ constructor( dismissDialog() var updateDeviceItemJob: Job? = null + var updateDialogUiJob: Job? = null job = coroutineScope.launch(mainDispatcher) { @@ -93,10 +93,9 @@ constructor( ) } ?: dialog!!.show() + updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { - // Add a slight delay for smoother dialog bounds change - delay(FIRST_LOAD_DELAY_MS) deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD) } @@ -128,11 +127,14 @@ constructor( deviceItemInteractor.deviceItemUpdate .onEach { - dialog!!.onDeviceItemUpdated( - it.take(MAX_DEVICE_ITEM_ENTRY), - showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY, - showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled - ) + updateDialogUiJob?.cancel() + updateDialogUiJob = launch { + dialog?.onDeviceItemUpdated( + it.take(MAX_DEVICE_ITEM_ENTRY), + showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY, + showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled + ) + } } .launchIn(this) @@ -153,6 +155,7 @@ constructor( bluetoothStateInteractor.isBluetoothEnabled, getSubtitleResId(bluetoothStateInteractor.isBluetoothEnabled), this@BluetoothTileDialogViewModel, + mainDispatcher, systemClock, uiEventLogger, logger, @@ -205,7 +208,6 @@ constructor( companion object { private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog" - private const val FIRST_LOAD_DELAY_MS = 500L private fun getSubtitleResId(isBluetoothEnabled: Boolean) = if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle else R.string.bt_is_off diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt index 2c8d2a0806d2..1c621b87533d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt @@ -49,5 +49,6 @@ data class DeviceItem( val connectionSummary: String = "", val iconWithDescription: Pair<Drawable, String>? = null, val background: Int? = null, - var isEnabled: Boolean = true + var isEnabled: Boolean = true, + var actionAccessibilityLabel: String = "", ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt index 7bb1619c5001..1c9be0f105b2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt @@ -28,6 +28,10 @@ private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy private val connected = R.string.quick_settings_bluetooth_device_connected private val saved = R.string.quick_settings_bluetooth_device_saved +private val actionAccessibilityLabelActivate = + R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate +private val actionAccessibilityLabelDisconnect = + R.string.accessibility_quick_settings_bluetooth_device_tap_to_disconnect /** Factories to create different types of Bluetooth device items from CachedBluetoothDevice. */ internal abstract class DeviceItemFactory { @@ -60,6 +64,7 @@ internal class ActiveMediaDeviceItemFactory : DeviceItemFactory() { }, background = backgroundOn, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect), ) } } @@ -87,6 +92,7 @@ internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() { }, background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate), ) } } @@ -112,6 +118,7 @@ internal class ConnectedDeviceItemFactory : DeviceItemFactory() { }, background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect), ) } } @@ -137,6 +144,7 @@ internal class SavedDeviceItemFactory : DeviceItemFactory() { }, background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/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 b05a5877e386..7201b3557a9b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -134,6 +134,7 @@ 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.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; @@ -1262,7 +1263,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 @@ -1315,7 +1316,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); } @@ -1336,7 +1337,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } updateClockAppearance(); mQsController.updateQsState(); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mNotificationStackScrollLayoutController.updateFooter(); } } @@ -1368,7 +1369,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); @@ -1480,7 +1481,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateMaxDisplayedNotifications(boolean recompute) { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { return; } @@ -1637,7 +1638,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()); } @@ -1651,7 +1652,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); @@ -1724,7 +1725,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; @@ -1899,7 +1900,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 @@ -2479,7 +2480,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); @@ -2937,7 +2938,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(); } } @@ -3189,7 +3190,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) { @@ -4411,7 +4412,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, @@ -4531,7 +4532,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); @@ -4592,7 +4593,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()); } @@ -4702,7 +4703,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); @@ -4744,7 +4745,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; } @@ -4910,7 +4911,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; } 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..5a4d8764759e 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; @@ -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; @@ -333,7 +331,6 @@ public class QuickSettingsController implements Dumpable { AccessibilityManager accessibilityManager, LockscreenGestureLogger lockscreenGestureLogger, MetricsLogger metricsLogger, - FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, ShadeLogger shadeLog, DumpManager dumpManager, @@ -384,7 +381,6 @@ public class QuickSettingsController implements Dumpable { mShadeLog = shadeLog; mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; mCastController = castController; - mFeatureFlags = featureFlags; mInteractionJankMonitor = interactionJankMonitor; mShadeRepository = shadeRepository; mShadeInteractor = shadeInteractor; @@ -1776,7 +1772,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 +1817,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); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 2ea7f61aee4c..f8bc0ee287e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -23,27 +23,37 @@ import androidx.annotation.ColorInt import androidx.collection.ArrayMap import androidx.lifecycle.lifecycleScope import com.android.internal.policy.SystemBarUtils +import com.android.internal.statusbar.StatusBarIcon import com.android.internal.util.ContrastColorUtil +import com.android.systemui.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 import com.android.systemui.statusbar.notification.collection.NotifCollection +import com.android.systemui.statusbar.notification.icon.IconPack import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onConfigChanged -import com.android.systemui.util.children +import com.android.systemui.util.asIndenting import com.android.systemui.util.kotlin.mapValuesNotNullTo -import com.android.systemui.util.kotlin.sample import com.android.systemui.util.kotlin.stateFlow +import com.android.systemui.util.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 @@ -62,11 +72,18 @@ object NotificationIconContainerViewBinder { viewModel: NotificationIconContainerShelfViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, + failureTracker: StatusBarIconViewBindingFailureTracker, viewStore: ShelfNotificationIconViewStore, ): DisposableHandle { return view.repeatWhenAttached { lifecycleScope.launch { - viewModel.icons.bindIcons(view, configuration, configurationController, viewStore) + viewModel.icons.bindIcons( + view, + configuration, + configurationController, + notifyBindingFailures = { failureTracker.shelfFailures = it }, + viewStore, + ) } } } @@ -77,18 +94,20 @@ object NotificationIconContainerViewBinder { viewModel: NotificationIconContainerStatusBarViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, + failureTracker: StatusBarIconViewBindingFailureTracker, viewStore: StatusBarNotificationIconViewStore, ): DisposableHandle { val contrastColorUtil = ContrastColorUtil.getInstance(view.context) return view.repeatWhenAttached { lifecycleScope.run { launch { - val iconColors = + val iconColors: Flow<NotificationIconColors> = viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) } viewModel.icons.bindIcons( view, configuration, configurationController, + notifyBindingFailures = { failureTracker.statusBarFailures = it }, viewStore, ) { _, sbiv -> StatusBarIconViewBinder.bindIconColors( @@ -110,6 +129,7 @@ object NotificationIconContainerViewBinder { viewModel: NotificationIconContainerAlwaysOnDisplayViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, + failureTracker: StatusBarIconViewBindingFailureTracker, viewStore: IconViewStore, ): DisposableHandle { return view.repeatWhenAttached { @@ -119,6 +139,7 @@ object NotificationIconContainerViewBinder { view, configuration, configurationController, + notifyBindingFailures = { failureTracker.aodFailures = it }, viewStore, ) { _, sbiv -> viewModel.bindAodStatusBarIconView(sbiv, configuration) @@ -176,7 +197,7 @@ object NotificationIconContainerViewBinder { } /** - * Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s [children]. + * Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s children. * * [bindIcon] will be invoked to bind a child [StatusBarIconView] to an icon associated with the * given `iconKey`. The parent [Job] of this coroutine will be cancelled automatically when the @@ -186,6 +207,7 @@ object NotificationIconContainerViewBinder { view: NotificationIconContainer, configuration: ConfigurationState, configurationController: ConfigurationController, + notifyBindingFailures: (Collection<String>) -> Unit, viewStore: IconViewStore, bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> }, ): Unit = coroutineScope { @@ -208,57 +230,59 @@ object NotificationIconContainerViewBinder { FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight) } - launch { - layoutParams.collect { params: FrameLayout.LayoutParams -> - for (child in view.children) { - child.layoutParams = params - } - } - } - - val iconBindings = mutableMapOf<String, Job>() + val failedBindings = mutableSetOf<String>() + val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>() var prevIcons = NotificationIconsViewData() - sample(layoutParams, ::Pair).collect { - (iconsData: NotificationIconsViewData, layoutParams: FrameLayout.LayoutParams), - -> + collect { iconsData: NotificationIconsViewData -> val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons) prevIcons = iconsData - val replacingIcons = - iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, v) -> - viewStore.iconView(v.notifKey).statusBarIcon + val replacingIcons: ArrayMap<String, StatusBarIcon> = + iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, info) -> + boundViewsByNotifKey[info.notifKey]?.first?.statusBarIcon } view.setReplacingIcons(replacingIcons) - val childrenByNotifKey: Map<String, StatusBarIconView> = - view.children.filterIsInstance<StatusBarIconView>().associateByTo(ArrayMap()) { - it.notification.key - } + for (notifKey in iconsDiff.removed) { + failedBindings.remove(notifKey) + val (child, job) = boundViewsByNotifKey.remove(notifKey) ?: continue + view.removeView(child) + job.cancel() + } - iconsDiff.removed - .mapNotNull { key -> childrenByNotifKey[key]?.let { key to it } } - .forEach { (key, child) -> - view.removeView(child) - iconBindings.remove(key)?.cancel() + 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 } - - val toAdd = iconsDiff.added.map { it.notifKey to viewStore.iconView(it.notifKey) } - for ((i, keyAndView) in toAdd.withIndex()) { - val (key, sbiv) = keyAndView - // The view might still be transiently added if it was just removed - // and added again + // The view might still be transiently added if it was just removed and added again view.removeTransientView(sbiv) - view.addView(sbiv, i, layoutParams) - iconBindings.remove(key)?.cancel() - iconBindings[key] = launch { bindIcon(key, sbiv) } + view.addView(sbiv, idx) + boundViewsByNotifKey.remove(notifKey)?.second?.cancel() + boundViewsByNotifKey[notifKey] = + Pair( + sbiv, + launch { + launch { layoutParams.collect { sbiv.layoutParams = it } } + bindIcon(notifKey, sbiv) + }, + ) } + notifyBindingFailures(failedBindings) + view.setChangingViewPositions(true) + // Re-sort notification icons + val expectedChildren = + iconsData.visibleKeys.mapNotNull { boundViewsByNotifKey[it.notifKey]?.first } val childCount = view.childCount for (i in 0 until childCount) { val actual = view.getChildAt(i) - val expected = viewStore.iconView(iconsData.visibleKeys[i].notifKey) + val expected = expectedChildren[i] if (actual === expected) { continue } @@ -273,47 +297,30 @@ object NotificationIconContainerViewBinder { /** External storage for [StatusBarIconView] instances. */ fun interface IconViewStore { - fun iconView(key: String): StatusBarIconView + fun iconView(key: String): StatusBarIconView? } @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE } /** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */ -class ShelfNotificationIconViewStore -@Inject -constructor( - private val notifCollection: NotifCollection, -) : IconViewStore { - override fun iconView(key: String): StatusBarIconView { - val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key") - return entry.icons.shelfIcon ?: error("No shelf IconView found for key: $key") - } -} +class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon }) /** [IconViewStore] for the always-on display. */ class AlwaysOnDisplayNotificationIconViewStore @Inject -constructor( - private val notifCollection: NotifCollection, -) : IconViewStore { - override fun iconView(key: String): StatusBarIconView { - val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key") - return entry.icons.aodIcon ?: error("No AOD IconView found for key: $key") - } -} +constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon }) /** [IconViewStore] for the status bar. */ -class StatusBarNotificationIconViewStore -@Inject -constructor( - private val notifCollection: NotifCollection, -) : IconViewStore { - override fun iconView(key: String): StatusBarIconView { - val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key") - return entry.icons.statusBarIcon ?: error("No status bar IconView found for key: $key") +class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon }) + +private fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) = + IconViewStore { key -> + getEntry(key)?.icons?.let(block) } -} private val View.viewBounds: Rect get() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt new file mode 100644 index 000000000000..0c114a2ac55d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.android.systemui.statusbar.notification.icon.ui.viewbinder + +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor +import com.android.systemui.util.asIndenting +import com.android.systemui.util.printCollection +import dagger.Binds +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import java.io.PrintWriter +import javax.inject.Inject + +@SysUISingleton +class StatusBarIconViewBindingFailureTracker @Inject constructor() : CoreStartable { + + var aodFailures: Collection<String> = emptyList() + var statusBarFailures: Collection<String> = emptyList() + var shelfFailures: Collection<String> = emptyList() + + // TODO(b/310681665): Ideally we wouldn't need to implement CoreStartable at all, and could just + // @Binds @IntoSet the Dumpable. + override fun start() { + // no-op, we're just using this as a dumpable + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + if (!NotificationIconContainerRefactor.isEnabled) return + pw.asIndenting().run { + printCollection("AOD Icon binding failures:", aodFailures) + printCollection("Status Bar Icon binding failures:", statusBarFailures) + printCollection("Shelf Icon binding failures:", shelfFailures) + } + } + + @dagger.Module + interface Module { + @Binds + @IntoMap + @ClassKey(StatusBarIconViewBindingFailureTracker::class) + fun bindStartable(impl: StatusBarIconViewBindingFailureTracker): CoreStartable + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt index 82626acc4b04..c03a4a5f5aab 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 @@ -31,6 +31,7 @@ import com.android.systemui.util.ui.toAnimatedValueFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map @@ -83,19 +84,18 @@ constructor( /** An Icon to show "isolated" in the IconContainer. */ val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> = headsUpIconInteractor.isolatedNotification + .combine(icons) { isolatedNotif, iconsViewData -> + isolatedNotif?.let { + iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif } + } + } .pairwise(initialValue = null) - .sample(combine(icons, shadeInteractor.shadeExpansion, ::Pair)) { - (prev, isolatedNotif), - (iconsViewData, shadeExpansion), - -> - val iconInfo = - isolatedNotif?.let { - iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif } - } + .distinctUntilChanged() + .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion -> val animate = when { - isolatedNotif == prev -> false - isolatedNotif == null || prev == null -> shadeExpansion == 0f + iconInfo?.notifKey == prev?.notifKey -> false + iconInfo == null || prev == null -> shadeExpansion == 0f else -> false } AnimatableEvent(iconInfo, animate) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index 538be142b8f2..9ff416a02ee6 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 @@ -43,9 +43,9 @@ import com.android.systemui.util.time.SystemClock class PeekDisabledSuppressor( private val globalSettings: GlobalSettings, private val headsUpManager: HeadsUpManager, - private val logger: NotificationInterruptLogger, + private val logger: VisualInterruptionDecisionLogger, @Main private val mainHandler: Handler, -) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek setting disabled") { +) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek disabled by global setting") { private var isEnabled = false override fun shouldSuppress(): Boolean = !isEnabled @@ -87,16 +87,13 @@ class PeekDisabledSuppressor( class PulseDisabledSuppressor( private val ambientDisplayConfiguration: AmbientDisplayConfiguration, private val userTracker: UserTracker, -) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse setting disabled") { +) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by user setting") { override fun shouldSuppress(): Boolean = !ambientDisplayConfiguration.pulseOnNotificationEnabled(userTracker.userId) } class PulseBatterySaverSuppressor(private val batteryController: BatteryController) : - VisualInterruptionCondition( - types = setOf(PULSE), - reason = "pulsing disabled by battery saver" - ) { + VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by battery saver") { override fun shouldSuppress() = batteryController.isAodPowerSave() } @@ -128,14 +125,14 @@ class PeekDndSuppressor() : } class PeekNotImportantSuppressor() : - VisualInterruptionFilter(types = setOf(PEEK), reason = "not important") { + VisualInterruptionFilter(types = setOf(PEEK), reason = "importance < HIGH") { override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_HIGH } class PeekDeviceNotInUseSuppressor( private val powerManager: PowerManager, private val statusBarStateController: StatusBarStateController -) : VisualInterruptionCondition(types = setOf(PEEK), reason = "not in use") { +) : VisualInterruptionCondition(types = setOf(PEEK), reason = "device not in use") { override fun shouldSuppress() = when { !powerManager.isScreenOn || statusBarStateController.isDreaming -> true @@ -144,7 +141,7 @@ class PeekDeviceNotInUseSuppressor( } class PeekOldWhenSuppressor(private val systemClock: SystemClock) : - VisualInterruptionFilter(types = setOf(PEEK), reason = "old when") { + VisualInterruptionFilter(types = setOf(PEEK), reason = "has old `when`") { private fun whenAge(entry: NotificationEntry) = systemClock.currentTimeMillis() - entry.sbn.notification.`when` @@ -165,21 +162,21 @@ class PeekOldWhenSuppressor(private val systemClock: SystemClock) : } class PulseEffectSuppressor() : - VisualInterruptionFilter(types = setOf(PULSE), reason = "ambient effect suppressed") { + VisualInterruptionFilter(types = setOf(PULSE), reason = "suppressed by DND") { override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressAmbient() } class PulseLockscreenVisibilityPrivateSuppressor() : VisualInterruptionFilter( types = setOf(PULSE), - reason = "notification hidden on lock screen by override" + reason = "hidden by lockscreen visibility override" ) { override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.lockscreenVisibilityOverride == VISIBILITY_PRIVATE } class PulseLowImportanceSuppressor() : - VisualInterruptionFilter(types = setOf(PULSE), reason = "importance less than DEFAULT") { + VisualInterruptionFilter(types = setOf(PULSE), reason = "importance < DEFAULT") { override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_DEFAULT } @@ -198,12 +195,12 @@ class HunJustLaunchedFsiSuppressor() : } class BubbleNotAllowedSuppressor() : - VisualInterruptionFilter(types = setOf(BUBBLE), reason = "not allowed") { + VisualInterruptionFilter(types = setOf(BUBBLE), reason = "cannot bubble") { override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble() } class BubbleNoMetadataSuppressor() : - VisualInterruptionFilter(types = setOf(BUBBLE), reason = "no bubble metadata") { + VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") { private fun isValidMetadata(metadata: BubbleMetadata?) = metadata != null && (metadata.intent != null || metadata.shortcutId != null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt index 6af25439ecc3..b44a36724bdb 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 @@ -50,19 +50,32 @@ class FullScreenIntentDecisionProvider( val shouldFsi: Boolean val wouldFsiWithoutDnd: Boolean val logReason: String + val shouldLog: Boolean + val isWarning: Boolean } private enum class DecisionImpl( override val shouldFsi: Boolean, override val logReason: String, override val wouldFsiWithoutDnd: Boolean = shouldFsi, - val supersedesDnd: Boolean = false + val supersedesDnd: Boolean = false, + override val shouldLog: Boolean = true, + override val isWarning: Boolean = false ) : Decision { - NO_FSI_NO_FULL_SCREEN_INTENT(false, "no full-screen intent", supersedesDnd = true), + NO_FSI_NO_FULL_SCREEN_INTENT( + false, + "no full-screen intent", + supersedesDnd = true, + shouldLog = false + ), NO_FSI_SHOW_STICKY_HUN(false, "full-screen intents are disabled", supersedesDnd = true), NO_FSI_NOT_IMPORTANT_ENOUGH(false, "not important enough"), - NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(false, "suppressive group alert behavior"), - NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false, "suppressive bubble metadata"), + NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR( + false, + "suppressive group alert behavior", + isWarning = true + ), + 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"), @@ -71,7 +84,7 @@ class FullScreenIntentDecisionProvider( FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"), FSI_LOCKED_SHADE(true, "locked shade"), FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"), - NO_FSI_NO_HUN_OR_KEYGUARD(false, "no HUN or keyguard"), + NO_FSI_NO_HUN_OR_KEYGUARD(false, "no HUN or keyguard", isWarning = true), 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/VisualInterruptionDecisionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt new file mode 100644 index 000000000000..1470b0331359 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.interruption + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING +import com.android.systemui.log.dagger.NotificationInterruptLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision +import com.android.systemui.statusbar.notification.logKey +import javax.inject.Inject + +class VisualInterruptionDecisionLogger +@Inject +constructor(@NotificationInterruptLog val buffer: LogBuffer) { + fun logHeadsUpFeatureChanged(isEnabled: Boolean) { + buffer.log( + TAG, + INFO, + { bool1 = isEnabled }, + { "HUN feature is now ${if (bool1) "enabled" else "disabled"}" } + ) + } + + fun logWillDismissAll() { + buffer.log(TAG, INFO, {}, { "dismissing all HUNs since feature was disabled" }) + } + + fun logDecision( + type: String, + entry: NotificationEntry, + decision: VisualInterruptionDecisionProvider.Decision + ) { + buffer.log( + TAG, + DEBUG, + { + str1 = type + bool1 = decision.shouldInterrupt + str2 = decision.logReason + str3 = entry.logKey + }, + { + val outcome = if (bool1) "allowed" else "suppressed" + "$str1 $outcome: $str2 (key=$str3)" + } + ) + } + + fun logFullScreenIntentDecision( + entry: NotificationEntry, + decision: FullScreenIntentDecision, + warning: Boolean + ) { + buffer.log( + TAG, + if (warning) WARNING else DEBUG, + { + bool1 = decision.shouldInterrupt + bool2 = decision.wouldInterruptWithoutDnd + str1 = decision.logReason + str2 = entry.logKey + }, + { + val outcome = + when { + bool1 -> "allowed" + bool2 -> "suppressed only by DND" + else -> "suppressed" + } + "FSI $outcome: $str1 (key=$str2)" + } + ) + } +} + +private const val TAG = "VisualInterruptionDecisionProvider" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index 9640682a8f39..c0a1a32b1401 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.interruption import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler import android.os.PowerManager +import android.util.Log import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -46,7 +47,7 @@ constructor( private val headsUpManager: HeadsUpManager, private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider, keyguardStateController: KeyguardStateController, - private val logger: NotificationInterruptLogger, + private val logger: VisualInterruptionDecisionLogger, @Main private val mainHandler: Handler, private val powerManager: PowerManager, private val statusBarStateController: StatusBarStateController, @@ -58,9 +59,32 @@ constructor( override val logReason: String ) : Decision + private data class LoggableDecision private constructor(val decision: DecisionImpl) { + companion object { + val unsuppressed = + LoggableDecision(DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")) + + fun suppressed(legacySuppressor: NotificationInterruptSuppressor, methodName: String) = + LoggableDecision( + DecisionImpl( + shouldInterrupt = false, + logReason = "${legacySuppressor.name}.$methodName" + ) + ) + + fun suppressed(suppressor: VisualInterruptionSuppressor) = + LoggableDecision( + DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason) + ) + } + } + private class FullScreenIntentDecisionImpl( + val entry: NotificationEntry, private val fsiDecision: FullScreenIntentDecisionProvider.Decision ) : FullScreenIntentDecision { + var hasBeenLogged = false + override val shouldInterrupt get() = fsiDecision.shouldFsi @@ -69,6 +93,12 @@ constructor( override val logReason get() = fsiDecision.logReason + + val shouldLog + get() = fsiDecision.shouldLog + + val isWarning + get() = fsiDecision.isWarning } private val fullScreenIntentDecisionProvider = @@ -139,137 +169,113 @@ constructor( override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision { check(started) - return makeHeadsUpDecision(entry) + + return if (statusBarStateController.isDozing) { + makeLoggablePulseDecision(entry) + } else { + makeLoggablePeekDecision(entry) + } + .decision } override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision { check(started) - return makeHeadsUpDecision(entry).also { logHeadsUpDecision(entry, it) } + + return if (statusBarStateController.isDozing) { + makeLoggablePulseDecision(entry).also { logDecision(PULSE, entry, it) } + } else { + makeLoggablePeekDecision(entry).also { logDecision(PEEK, entry, it) } + } + .decision } + private fun makeLoggablePeekDecision(entry: NotificationEntry): LoggableDecision = + checkConditions(PEEK) + ?: checkFilters(PEEK, entry) ?: checkSuppressInterruptions(entry) + ?: checkSuppressAwakeInterruptions(entry) ?: checkSuppressAwakeHeadsUp(entry) + ?: LoggableDecision.unsuppressed + + private fun makeLoggablePulseDecision(entry: NotificationEntry): LoggableDecision = + checkConditions(PULSE) + ?: checkFilters(PULSE, entry) ?: checkSuppressInterruptions(entry) + ?: LoggableDecision.unsuppressed + override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision { check(started) - return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) } + + return makeLoggableBubbleDecision(entry).also { logDecision(BUBBLE, entry, it) }.decision + } + + private fun makeLoggableBubbleDecision(entry: NotificationEntry): LoggableDecision = + checkConditions(BUBBLE) + ?: checkFilters(BUBBLE, entry) ?: checkSuppressInterruptions(entry) + ?: checkSuppressAwakeInterruptions(entry) ?: LoggableDecision.unsuppressed + + private fun logDecision( + type: VisualInterruptionType, + entry: NotificationEntry, + loggable: LoggableDecision + ) { + logger.logDecision(type.name, entry, loggable.decision) } override fun makeUnloggedFullScreenIntentDecision( entry: NotificationEntry ): FullScreenIntentDecision { check(started) - return makeFullScreenIntentDecision(entry) + + val couldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt + val fsiDecision = + fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, couldHeadsUp) + return FullScreenIntentDecisionImpl(entry, fsiDecision) } override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) { check(started) - // Not yet implemented. - } - private fun makeHeadsUpDecision(entry: NotificationEntry): DecisionImpl { - if (statusBarStateController.isDozing) { - return makePulseDecision(entry) - } else { - return makePeekDecision(entry) + if (decision !is FullScreenIntentDecisionImpl) { + Log.wtf(TAG, "FSI decision $decision was not created by this class") + return } - } - private fun makePeekDecision(entry: NotificationEntry): DecisionImpl { - checkConditions(PEEK)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkFilters(PEEK, entry)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressInterruptions" - ) - } - checkAwakeSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressAwakeInterruptions" - ) + if (decision.hasBeenLogged) { + Log.wtf(TAG, "FSI decision $decision has already been logged") + return } - checkAwakeHeadsUpSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressAwakeHeadsUpInterruptions" - ) - } - return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed") - } - private fun makePulseDecision(entry: NotificationEntry): DecisionImpl { - checkConditions(PULSE)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkFilters(PULSE, entry)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressInterruptions" - ) - } - return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed") - } + decision.hasBeenLogged = true - private fun makeBubbleDecision(entry: NotificationEntry): DecisionImpl { - checkConditions(BUBBLE)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) + if (!decision.shouldLog) { + return } - checkFilters(BUBBLE, entry)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressInterruptions" - ) - } - checkAwakeSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressAwakeInterruptions" - ) - } - return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed") - } - private fun logHeadsUpDecision(entry: NotificationEntry, decision: DecisionImpl) { - // Not yet implemented. + logger.logFullScreenIntentDecision(decision.entry, decision, decision.isWarning) } - private fun logBubbleDecision(entry: NotificationEntry, decision: DecisionImpl) { - // Not yet implemented. - } - - private fun makeFullScreenIntentDecision(entry: NotificationEntry): FullScreenIntentDecision { - val wouldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt - val fsiDecision = - fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, wouldHeadsUp) - return FullScreenIntentDecisionImpl(fsiDecision) - } - - private fun checkSuppressors(entry: NotificationEntry) = - legacySuppressors.firstOrNull { it.suppressInterruptions(entry) } - - private fun checkAwakeSuppressors(entry: NotificationEntry) = - legacySuppressors.firstOrNull { it.suppressAwakeInterruptions(entry) } - - private fun checkAwakeHeadsUpSuppressors(entry: NotificationEntry) = - legacySuppressors.firstOrNull { it.suppressAwakeHeadsUp(entry) } - - private fun checkConditions(type: VisualInterruptionType): VisualInterruptionCondition? = - conditions.firstOrNull { it.types.contains(type) && it.shouldSuppress() } - - private fun checkFilters( - type: VisualInterruptionType, - entry: NotificationEntry - ): VisualInterruptionFilter? = - filters.firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) } + private fun checkSuppressInterruptions(entry: NotificationEntry) = + legacySuppressors + .firstOrNull { it.suppressInterruptions(entry) } + ?.let { LoggableDecision.suppressed(it, "suppressInterruptions") } + + private fun checkSuppressAwakeInterruptions(entry: NotificationEntry) = + legacySuppressors + .firstOrNull { it.suppressAwakeInterruptions(entry) } + ?.let { LoggableDecision.suppressed(it, "suppressAwakeInterruptions") } + + private fun checkSuppressAwakeHeadsUp(entry: NotificationEntry) = + legacySuppressors + .firstOrNull { it.suppressAwakeHeadsUp(entry) } + ?.let { LoggableDecision.suppressed(it, "suppressAwakeHeadsUp") } + + private fun checkConditions(type: VisualInterruptionType) = + conditions + .firstOrNull { it.types.contains(type) && it.shouldSuppress() } + ?.let { LoggableDecision.suppressed(it) } + + private fun checkFilters(type: VisualInterruptionType, entry: NotificationEntry) = + filters + .firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) } + ?.let { LoggableDecision.suppressed(it) } } private const val TAG = "VisualInterruptionDecisionProviderImpl" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index 7667e17cac14..5cdead407891 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -24,6 +24,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel @@ -40,6 +41,7 @@ object NotificationShelfViewBinder { configuration: ConfigurationState, configurationController: ConfigurationController, falsingManager: FalsingManager, + iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, notificationIconAreaController: NotificationIconAreaController, shelfIconViewStore: ShelfNotificationIconViewStore, ) { @@ -51,6 +53,7 @@ object NotificationShelfViewBinder { viewModel.icons, configuration, configurationController, + iconViewBindingFailureTracker, shelfIconViewStore, ) } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/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/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index 6cf56102d65f..a5b87f088578 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController @@ -45,6 +46,7 @@ constructor( private val configurationController: ConfigurationController, private val falsingManager: FalsingManager, private val iconAreaController: NotificationIconAreaController, + private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val shelfIconViewStore: ShelfNotificationIconViewStore, ) { @@ -68,6 +70,7 @@ constructor( configuration, configurationController, falsingManager, + iconViewBindingFailureTracker, iconAreaController, shelfIconViewStore, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt new file mode 100644 index 000000000000..bcf732214d57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.ui.viewbinder + +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker +import dagger.Module + +@Module(includes = [StatusBarIconViewBindingFailureTracker.Module::class]) +object StatusBarNotificationViewBinderModule diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 2e5717de1a86..4586f58c5837 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; @@ -1449,7 +1450,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return (v, event) -> { mAutoHideController.checkUserAutoHide(event); mRemoteInputManager.checkRemoteInputOutside(event); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mShadeController.onStatusBarTouch(event); } return getNotificationShadeWindowView().onTouchEvent(event); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index beeee1be401d..495b4e1e14cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -252,7 +252,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar } if (NotificationIconContainerRefactor.isEnabled()) { mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey( - newEntry == null ? null : newEntry.getKey()); + newEntry == null ? null : newEntry.getRepresentativeEntry().getKey()); } else { updateIsolatedIconLocation(false /* requireUpdate */); mNotificationIconAreaController.showIconIsolated(newEntry == null ? null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java index 3b3d8b6b2109..1f9952a8d4ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java @@ -40,7 +40,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -115,7 +115,6 @@ public class LegacyNotificationIconAreaControllerImpl implements private int mAodIconTint; private boolean mAodIconsVisible; private boolean mShowLowPriority = true; - private boolean mIsStatusViewMigrated = false; @VisibleForTesting final NotificationListener.NotificationSettingsListener mSettingsListener = @@ -159,7 +158,6 @@ public class LegacyNotificationIconAreaControllerImpl implements mStatusBarWindowController = statusBarWindowController; mScreenOffAnimationController = screenOffAnimationController; notificationListener.addNotificationSettingsListener(mSettingsListener); - mIsStatusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW); initializeNotificationAreaViews(context); reloadAodColor(); darkIconDispatcher.addDarkReceiver(this); @@ -551,7 +549,7 @@ public class LegacyNotificationIconAreaControllerImpl implements return; } if (mScreenOffAnimationController.shouldAnimateAodIcons()) { - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.setTranslationY(-mAodIconAppearTranslation); } mAodIcons.setAlpha(0); @@ -563,14 +561,14 @@ public class LegacyNotificationIconAreaControllerImpl implements .start(); } else { mAodIcons.setAlpha(1.0f); - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.setTranslationY(0); } } } private void animateInAodIconTranslation() { - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.animate() .setInterpolator(Interpolators.DECELERATE_QUINT) .translationY(0) @@ -673,7 +671,7 @@ public class LegacyNotificationIconAreaControllerImpl implements } } else { mAodIcons.setAlpha(1.0f); - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.setTranslationY(0); } mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index fb586eac5ab7..1a17e7c28410 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -20,6 +20,7 @@ import com.android.app.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.shade.ShadeViewController import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealScrim @@ -34,8 +35,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.app.tracing.TraceUtils import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags /** * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely @@ -68,7 +67,6 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val interactionJankMonitor: InteractionJankMonitor, private val powerManager: PowerManager, private val handler: Handler = Handler(), - private val featureFlags: FeatureFlags, ) : WakefulnessLifecycle.Observer, ScreenOffAnimation { private lateinit var centralSurfaces: CentralSurfaces private lateinit var shadeViewController: ShadeViewController @@ -288,7 +286,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // up, with unpredictable consequences. if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) && shouldAnimateInKeyguard) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { // Tracking this state should no longer be relevant, as the isInteractive // check covers it aodUiAnimationPlaying = true diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 3921e69501d2..7adc08ca00c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -56,6 +56,7 @@ import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableSta import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; @@ -217,6 +218,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mWaitingForWindowStateChangeAfterCameraLaunch = false; mTransitionFromLockscreenToDreamStarted = false; }; + private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker; @Inject public CollapsedStatusBarFragment( @@ -235,6 +237,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue KeyguardStateController keyguardStateController, ShadeViewController shadeViewController, StatusBarStateController statusBarStateController, + StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker, CommandQueue commandQueue, CarrierConfigTracker carrierConfigTracker, CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, @@ -264,6 +267,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mKeyguardStateController = keyguardStateController; mShadeViewController = shadeViewController; mStatusBarStateController = statusBarStateController; + mIconViewBindingFailureTracker = iconViewBindingFailureTracker; mCommandQueue = commandQueue; mCarrierConfigTracker = carrierConfigTracker; mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger; @@ -471,6 +475,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBarIconsViewModel, mConfigurationState, mConfigurationController, + mIconViewBindingFailureTracker, mStatusBarIconViewStore); } else { mNotificationIconAreaInner = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index b614b6d0547d..78954dea27ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -35,13 +35,12 @@ import com.android.keyguard.KeyguardConstants; import com.android.keyguard.KeyguardVisibilityHelper; import com.android.keyguard.dagger.KeyguardUserSwitcherScope; import com.android.settingslib.drawable.CircleFramedDrawable; -import com.android.systemui.res.R; import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.user.UserSwitchDialogController; +import com.android.systemui.res.R; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -149,7 +148,6 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, UserSwitchDialogController userSwitchDialogController, - FeatureFlags featureFlags, UiEventLogger uiEventLogger) { super(view); if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); @@ -163,7 +161,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ false, - featureFlags, /* logBuffer= */ null); + /* logBuffer= */ null); mUserSwitchDialogController = userSwitchDialogController; mUiEventLogger = uiEventLogger; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index dfe26865f978..770f441799b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -39,7 +39,6 @@ import com.android.keyguard.KeyguardVisibilityHelper; import com.android.keyguard.dagger.KeyguardUserSwitcherScope; import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; @@ -161,7 +160,6 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - FeatureFlags featureFlags, DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController) { super(keyguardUserSwitcherView); @@ -177,7 +175,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ false, - featureFlags, /* logBuffer= */ null); + /* logBuffer= */ null); mBackground = new KeyguardUserSwitcherScrim(context); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 818ba9512e15..3304b9827fd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository; import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl; import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepositoryModule; +import com.android.systemui.statusbar.policy.data.repository.ZenModeRepositoryModule; import dagger.Binds; import dagger.Module; @@ -81,7 +82,7 @@ import java.util.concurrent.Executor; import javax.inject.Named; /** Dagger Module for code in the statusbar.policy package. */ -@Module(includes = { DeviceProvisioningRepositoryModule.class }) +@Module(includes = { DeviceProvisioningRepositoryModule.class, ZenModeRepositoryModule.class }) public interface StatusBarPolicyModule { String DEVICE_STATE_ROTATION_LOCK_DEFAULTS = "DEVICE_STATE_ROTATION_LOCK_DEFAULTS"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt new file mode 100644 index 000000000000..94ab58ae5a3d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.data.repository + +import android.app.NotificationManager +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.statusbar.policy.ZenModeController +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** + * A repository that holds information about the status and configuration of Zen Mode (or Do Not + * Disturb/DND Mode). + */ +interface ZenModeRepository { + val zenMode: Flow<Int> + val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?> +} + +class ZenModeRepositoryImpl +@Inject +constructor( + private val zenModeController: ZenModeController, +) : ZenModeRepository { + // TODO(b/308591859): ZenModeController should use flows instead of callbacks. The + // conflatedCallbackFlows here should be replaced eventually, see: + // https://docs.google.com/document/d/1gAiuYupwUAFdbxkDXa29A4aFNu7XoCd7sCIk31WTnHU/edit?resourcekey=0-J4ZBiUhLhhQnNobAcI2vIw + + override val zenMode: Flow<Int> = conflatedCallbackFlow { + val callback = + object : ZenModeController.Callback { + override fun onZenChanged(zen: Int) { + trySend(zen) + } + } + zenModeController.addCallback(callback) + trySend(zenModeController.zen) + + awaitClose { zenModeController.removeCallback(callback) } + } + + override val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?> = + conflatedCallbackFlow { + val callback = + object : ZenModeController.Callback { + override fun onConsolidatedPolicyChanged(policy: NotificationManager.Policy?) { + trySend(policy) + } + } + zenModeController.addCallback(callback) + trySend(zenModeController.consolidatedPolicy) + + awaitClose { zenModeController.removeCallback(callback) } + } +} + +@Module +interface ZenModeRepositoryModule { + @Binds fun bindImpl(impl: ZenModeRepositoryImpl): ZenModeRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt new file mode 100644 index 000000000000..ae31851cb8f5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.domain.interactor + +import android.provider.Settings +import com.android.systemui.statusbar.policy.data.repository.ZenModeRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +/** + * An interactor that performs business logic related to the status and configuration of Zen Mode + * (or Do Not Disturb/DND Mode). + */ +class ZenModeInteractor @Inject constructor(repository: ZenModeRepository) { + val isZenModeEnabled: Flow<Boolean> = + repository.zenMode + .map { + when (it) { + Settings.Global.ZEN_MODE_ALARMS -> true + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> true + Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> true + Settings.Global.ZEN_MODE_OFF -> false + else -> false + } + } + .distinctUntilChanged() + + val areNotificationsHiddenInShade: Flow<Boolean> = + combine(isZenModeEnabled, repository.consolidatedNotificationPolicy) { dndEnabled, policy -> + if (!dndEnabled) { + false + } else { + val showInNotificationList = policy?.showInNotificationList() ?: true + !showInNotificationList + } + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt new file mode 100644 index 000000000000..4cbdd6fd6488 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.ui.binder + +import com.android.systemui.statusbar.notification.ui.viewbinder.StatusBarNotificationViewBinderModule +import dagger.Module + +@Module(includes = [StatusBarNotificationViewBinderModule::class]) object StatusBarViewBinderModule diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt index fc414b66b042..2f2c4b0530fb 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt @@ -39,7 +39,6 @@ import androidx.annotation.VisibleForTesting import com.android.app.animation.Interpolators import com.android.internal.widget.CachingIconView import com.android.systemui.Gefingerpoken -import com.android.systemui.res.R import com.android.systemui.classifier.FalsingCollector import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Text.Companion.loadText @@ -48,9 +47,8 @@ import com.android.systemui.common.ui.binder.TintedIconViewBinder import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryViewDisplayController @@ -96,7 +94,6 @@ constructor( wakeLockBuilder: WakeLock.Builder, systemClock: SystemClock, tempViewUiEventLogger: TemporaryViewUiEventLogger, - private val featureFlags: FeatureFlags, ) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>( context, @@ -234,18 +231,14 @@ constructor( maybeGetAccessibilityFocus(newInfo, currentView) // ---- Haptics ---- - if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - vibratorHelper.performHapticFeedback(parent, newInfo.vibrationConstant) - } else { - newInfo.vibrationEffect?.let { - vibratorHelper.vibrate( - Process.myUid(), - context.getApplicationContext().getPackageName(), - it, - newInfo.windowTitle, - VIBRATION_ATTRIBUTES, - ) - } + newInfo.vibrationEffect?.let { + vibratorHelper.vibrate( + Process.myUid(), + context.getApplicationContext().getPackageName(), + it, + newInfo.windowTitle, + VIBRATION_ATTRIBUTES, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt index 4449f8d06f15..7475388e80c2 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt @@ -17,13 +17,12 @@ package com.android.systemui.temporarydisplay.chipbar import android.os.VibrationEffect -import android.view.HapticFeedbackConstants import android.view.View import androidx.annotation.AttrRes import com.android.internal.logging.InstanceId -import com.android.systemui.res.R import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon +import com.android.systemui.res.R import com.android.systemui.temporarydisplay.TemporaryViewInfo import com.android.systemui.temporarydisplay.ViewPriority @@ -43,7 +42,6 @@ data class ChipbarInfo( val text: Text, val endItem: ChipbarEndItem?, val vibrationEffect: VibrationEffect? = null, - val vibrationConstant: Int = HapticFeedbackConstants.NO_HAPTICS, val allowSwipeToDismiss: Boolean = false, override val windowTitle: String, override val wakeReason: String, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 4d8768f5e9e0..2e9b7e84344b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -127,7 +127,6 @@ class ClockEventControllerTest : SysuiTestCase() { withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) - set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false) set(Flags.FACE_AUTH_REFACTOR, false) } underTest = diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index a38ba00d898f..6a08eeac8108 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -21,7 +21,6 @@ import static android.view.View.INVISIBLE; import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.flags.Flags.MIGRATE_CLOCKS_TO_BLUEPRINT; -import static com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; @@ -60,6 +59,7 @@ import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; @@ -181,7 +181,6 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { mFakeFeatureFlags = new FakeFeatureFlags(); mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false); mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); - mFakeFeatureFlags.set(MIGRATE_KEYGUARD_STATUS_VIEW, false); mFakeFeatureFlags.set(MIGRATE_CLOCKS_TO_BLUEPRINT, false); mController = new KeyguardClockSwitchController( mView, @@ -192,6 +191,7 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { mSmartspaceController, mock(ConfigurationController.class), mock(ScreenOffAnimationController.class), + mock(StatusBarIconViewBindingFailureTracker.class), mKeyguardUnlockAnimationController, mSecureSettings, mExecutor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 22c75d85d4a1..146715d26b7d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -30,7 +30,6 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -60,7 +59,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected ScreenOffAnimationController mScreenOffAnimationController; @Mock protected KeyguardLogger mKeyguardLogger; @Mock protected KeyguardStatusViewController mControllerMock; - @Mock protected FeatureFlags mFeatureFlags; @Mock protected InteractionJankMonitor mInteractionJankMonitor; @Mock protected ViewTreeObserver mViewTreeObserver; @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -91,7 +89,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { mDozeParameters, mScreenOffAnimationController, mKeyguardLogger, - mFeatureFlags, mInteractionJankMonitor, deps.getKeyguardInteractor(), mKeyguardTransitionInteractor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 776799ec054e..2b41e08065d1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -3436,7 +3436,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus); when(mUsbPort.supportsComplianceWarnings()).thenReturn(true); when(mUsbPortStatus.isConnected()).thenReturn(true); - when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1}); + when(mUsbPortStatus.getComplianceWarnings()) + .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY}); } private Context getSpyContext() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index ae2ec2ca1fe9..87ab5b0d157f 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 @@ -143,6 +143,23 @@ class AuthenticationRepositoryTest : SysuiTestCase() { assertThat(authenticationChallengeResults).isEqualTo(listOf(true, false, true)) } + @Test + fun isPinEnhancedPrivacyEnabled() = + testScope.runTest { + whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[0].id)) + .thenReturn(false) + whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[1].id)) + .thenReturn(true) + + val values by collectValues(underTest.isPinEnhancedPrivacyEnabled) + assertThat(values.first()).isTrue() + assertThat(values.last()).isFalse() + + userRepository.setSelectedUserInfo(USER_INFOS[1]) + assertThat(values.last()).isTrue() + + } + private fun setSecurityModeAndDispatchBroadcast( securityMode: KeyguardSecurityModel.SecurityMode, ) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt index d2b81e06c0e5..00ea78f01fa9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt @@ -17,14 +17,14 @@ package com.android.systemui.biometrics import androidx.test.filters.SmallTest -import com.android.SysUITestComponent -import com.android.SysUITestModule -import com.android.runCurrent -import com.android.runTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FakeFeatureFlagsClassicModule import com.android.systemui.flags.Flags +import com.android.systemui.runCurrent +import com.android.systemui.runTest import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.user.domain.UserDomainLayerModule import dagger.BindsInstance diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 6da69519000c..7a9cb6cc18c2 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 @@ -354,6 +354,18 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden) } + @Test + fun isDigitButtonAnimationEnabled() = + testScope.runTest { + val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled) + + utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true) + assertThat(isAnimationEnabled).isFalse() + + utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false) + assertThat(isAnimationEnabled).isTrue() + } + private fun TestScope.lockDeviceAndOpenPinBouncer() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.deviceEntryRepository.setUnlocked(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt new file mode 100644 index 000000000000..e8eda8096b1e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.deviceentry.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceEntryUdfpsInteractorTest : SysuiTestCase() { + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository + private lateinit var biometricsRepository: FakeBiometricSettingsRepository + + private lateinit var underTest: DeviceEntryUdfpsInteractor + + @Before + fun setUp() { + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + biometricsRepository = FakeBiometricSettingsRepository() + + underTest = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = fingerprintAuthRepository, + biometricSettingsRepository = biometricsRepository, + ) + } + + @Test + fun udfpsSupported_rearFp_false() = runTest { + val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported) + fingerprintPropertyRepository.supportsRearFps() + assertThat(isUdfpsSupported).isFalse() + } + + @Test + fun udfpsSupoprted() = runTest { + val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported) + fingerprintPropertyRepository.supportsUdfps() + assertThat(isUdfpsSupported).isTrue() + } + + @Test + fun udfpsEnrolledAndEnabled() = runTest { + val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled) + fingerprintPropertyRepository.supportsUdfps() + biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + assertThat(isUdfpsEnrolledAndEnabled).isTrue() + } + + @Test + fun udfpsEnrolledAndEnabled_rearFp_false() = runTest { + val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled) + fingerprintPropertyRepository.supportsRearFps() + biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + assertThat(isUdfpsEnrolledAndEnabled).isFalse() + } + + @Test + fun udfpsEnrolledAndEnabled_notEnrolledOrEnabled_false() = runTest { + val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled) + fingerprintPropertyRepository.supportsUdfps() + biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + assertThat(isUdfpsEnrolledAndEnabled).isFalse() + } + + @Test + fun isListeningForUdfps() = runTest { + val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps) + fingerprintPropertyRepository.supportsUdfps() + fingerprintAuthRepository.setIsRunning(true) + assertThat(isListeningForUdfps).isTrue() + } + + @Test + fun isListeningForUdfps_rearFp_false() = runTest { + val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps) + fingerprintPropertyRepository.supportsRearFps() + fingerprintAuthRepository.setIsRunning(true) + assertThat(isListeningForUdfps).isFalse() + } + + @Test + fun isListeningForUdfps_notRunning_false() = runTest { + val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps) + fingerprintPropertyRepository.supportsUdfps() + fingerprintAuthRepository.setIsRunning(false) + assertThat(isListeningForUdfps).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt index 5eab2fc73fe6..df52265384fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt @@ -84,8 +84,8 @@ class BurnInInteractorTest : SysuiTestCase() { @Test fun udfpsBurnInOffset_updatesOnResolutionScaleChange() = testScope.runTest { - val udfpsBurnInOffsetX by collectLastValue(underTest.udfpsBurnInXOffset) - val udfpsBurnInOffsetY by collectLastValue(underTest.udfpsBurnInYOffset) + val udfpsBurnInOffsetX by collectLastValue(underTest.deviceEntryIconXOffset) + val udfpsBurnInOffsetY by collectLastValue(underTest.deviceEntryIconYOffset) assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset) assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset) @@ -101,7 +101,7 @@ class BurnInInteractorTest : SysuiTestCase() { @Test fun udfpsBurnInProgress_updatesOnDozeTimeTick() = testScope.runTest { - val udfpsBurnInProgress by collectLastValue(underTest.udfpsBurnInProgress) + val udfpsBurnInProgress by collectLastValue(underTest.udfpsProgress) assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress) setBurnInProgress(.88f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt index b439fcf8c98a..722c11d9f34c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt @@ -18,9 +18,9 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.SysUITestModule -import com.android.TestMocksModule +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule import com.android.systemui.coroutines.collectValues import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository 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..3d094fd724d3 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 @@ -18,9 +18,9 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.SysUITestModule -import com.android.TestMocksModule +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule import com.android.systemui.coroutines.collectValues import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt index 3442df62a441..2dfc13258d63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt @@ -123,30 +123,30 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { runCurrent() // THEN burn in offsets are 0 - assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f) - assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0) - assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0) + assertThat(burnInOffsets?.progress).isEqualTo(0f) + assertThat(burnInOffsets?.y).isEqualTo(0) + assertThat(burnInOffsets?.x).isEqualTo(0) // WHEN we're in the middle of the doze amount change keyguardRepository.setDozeAmount(.50f) runCurrent() // THEN burn in is updated (between 0 and the full offset) - assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f) - assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0) - assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0) - assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress) - assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset) - assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset) + assertThat(burnInOffsets?.progress).isGreaterThan(0f) + assertThat(burnInOffsets?.y).isGreaterThan(0) + assertThat(burnInOffsets?.x).isGreaterThan(0) + assertThat(burnInOffsets?.progress).isLessThan(burnInProgress) + assertThat(burnInOffsets?.y).isLessThan(burnInYOffset) + assertThat(burnInOffsets?.x).isLessThan(burnInXOffset) // WHEN we're fully dozing keyguardRepository.setDozeAmount(1f) runCurrent() // THEN burn in offsets are updated to final current values (for the given time) - assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress) - assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset) - assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset) + assertThat(burnInOffsets?.progress).isEqualTo(burnInProgress) + assertThat(burnInOffsets?.y).isEqualTo(burnInYOffset) + assertThat(burnInOffsets?.x).isEqualTo(burnInXOffset) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt index 570dfb3f0a9e..e9399cc17158 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.ui.binder import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.SysUITestModule +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 43d70adf26b0..76c258935727 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -30,7 +30,6 @@ import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.items.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection @@ -49,6 +48,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import java.util.Optional @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) @@ -60,7 +60,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var mDefaultDeviceEntryIconSection: DefaultDeviceEntryIconSection @Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection @Mock - private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection + private lateinit var defaultAmbientIndicationAreaSection: Optional<KeyguardSection> @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection @Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection @Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection @@ -98,7 +98,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { fun replaceViews() { val constraintLayout = ConstraintLayout(context, null) underTest.replaceViews(null, constraintLayout) - underTest.sections.forEach { verify(it).addViews(constraintLayout) } + underTest.sections.forEach { verify(it)?.addViews(constraintLayout) } } @Test @@ -110,7 +110,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { val constraintLayout = ConstraintLayout(context, null) underTest.replaceViews(prevBlueprint, constraintLayout) underTest.sections.minus(mDefaultDeviceEntryIconSection).forEach { - verify(it, never()).addViews(constraintLayout) + verify(it, never())?.addViews(constraintLayout) } verify(mDefaultDeviceEntryIconSection).addViews(constraintLayout) @@ -121,6 +121,6 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { fun applyConstraints() { val cs = ConstraintSet() underTest.applyConstraints(cs) - underTest.sections.forEach { verify(it).applyConstraints(cs) } + underTest.sections.forEach { verify(it)?.applyConstraints(cs) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt index 71313c8a5bf3..75bdcddf516b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt @@ -24,12 +24,14 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.LockIconViewController +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R @@ -42,6 +44,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Answers import org.mockito.Mock +import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi @@ -77,7 +80,9 @@ class DefaultDeviceEntryIconSectionTest : SysuiTestCase() { notificationPanelView, featureFlags, { lockIconViewController }, - { DeviceEntryIconViewModel() }, + { mock(DeviceEntryIconViewModel::class.java) }, + { mock(DeviceEntryForegroundViewModel::class.java) }, + { mock(DeviceEntryBackgroundViewModel::class.java) }, { falsingManager }, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt new file mode 100644 index 000000000000..f282481ba01c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodToGoneTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: AodToGoneTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = AodToGoneTransitionViewModel(interactor) + } + + @Test + fun deviceEntryParentViewHides() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + value = value, + transitionState = state, + ownerName = "AodToGoneTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..517149c99a62 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodToLockscreenTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: AodToLockscreenTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + underTest = + AodToLockscreenTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = FakeBiometricSettingsRepository(), + ), + ) + } + + @Test + fun deviceEntryParentViewShows() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(1f) } + } + + @Test + fun deviceEntryBackgroundView_udfps_alphaFadeIn() = runTest { + fingerprintPropertyRepository.supportsUdfps() + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // fade in + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(.2f) + + repository.sendTransitionStep(step(0.3f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(.6f) + + repository.sendTransitionStep(step(0.6f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryBackgroundView_rearFp_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + // no updates + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(0.1f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(0.3f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(0.6f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ownerName = "AodToLockscreenTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt new file mode 100644 index 000000000000..96f69462accf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodToOccludedTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: AodToOccludedTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = AodToOccludedTransitionViewModel(interactor) + } + + @Test + fun deviceEntryParentViewHides() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { Truth.assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.OCCLUDED, + value = value, + transitionState = state, + ownerName = "AodToOccludedTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..5dccc3b1d05f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class DozingToLockscreenTransitionViewModelTest : SysuiTestCase() { + private lateinit var testScope: TestScope + private lateinit var underTest: DozingToLockscreenTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + underTest = + DozingToLockscreenTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + ) + } + + @Test + fun deviceEntryParentViewShows() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(1f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.DOZING, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ownerName = "DozingToLockscreenTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index 6d47aed58dac..fd125e099f1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -19,6 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState.AOD @@ -35,6 +41,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.mockito.mock import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -44,22 +51,34 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: DreamingToLockscreenTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository @Before fun setUp() { repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() val interactor = KeyguardTransitionInteractorFactory.create( scope = TestScope().backgroundScope, repository = repository, ) .keyguardTransitionInteractor - underTest = DreamingToLockscreenTransitionViewModel(interactor, mock()) + underTest = + DreamingToLockscreenTransitionViewModel( + interactor, + mock(), + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = FakeBiometricSettingsRepository(), + ), + ) } @Test @@ -129,6 +148,78 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { } @Test + fun deviceEntryParentViewFadeIn() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.deviceEntryParentViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, STARTED)) + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + + job.cancel() + } + + @Test + fun deviceEntryBackgroundViewAppear() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.UDFPS_OPTICAL, + sensorLocations = emptyMap(), + ) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, STARTED)) + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + values.forEach { assertThat(it).isEqualTo(1f) } + + job.cancel() + } + + @Test + fun deviceEntryBackground_noUdfps_noUpdates() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.REAR, + sensorLocations = emptyMap(), + ) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, STARTED)) + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(0) // no updates + + job.cancel() + } + + @Test fun lockscreenTranslationY() = runTest(UnconfinedTestDispatcher()) { val values = mutableListOf<Float>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt index 255f4df17244..c1444a55f7d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt @@ -19,7 +19,11 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState @@ -27,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -34,11 +39,14 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class GoneToAodTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: GoneToAodTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository private lateinit var testScope: TestScope @Before @@ -47,13 +55,24 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { testScope = TestScope(testDispatcher) repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = testScope.backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = GoneToAodTransitionViewModel(interactor) + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + + underTest = + GoneToAodTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) } @Test @@ -63,11 +82,11 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { val enterFromTopTranslationY by collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt())) - // The animation should only start > halfway through + // The animation should only start > .4f way through repository.sendTransitionStep(step(0f, TransitionState.STARTED)) assertThat(enterFromTopTranslationY).isEqualTo(pixels) - repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.4f)) assertThat(enterFromTopTranslationY).isEqualTo(pixels) repository.sendTransitionStep(step(.85f)) @@ -83,11 +102,11 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha) - // The animation should only start > halfway through + // The animation should only start > .4f way through repository.sendTransitionStep(step(0f, TransitionState.STARTED)) assertThat(enterFromTopAnimationAlpha).isEqualTo(0f) - repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.4f)) assertThat(enterFromTopAnimationAlpha).isEqualTo(0f) repository.sendTransitionStep(step(.85f)) @@ -97,6 +116,98 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { assertThat(enterFromTopAnimationAlpha).isEqualTo(1f) } + @Test + fun deviceEntryBackgroundViewAlpha() = + testScope.runTest { + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled() = + testScope.runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isIn(Range.closed(.01f, 1f)) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isIn(Range.closed(.99f, 1f)) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFpEnrolled() = + testScope.runTest { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + @Test + fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = + testScope.runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + private fun step( value: Float, state: TransitionState = TransitionState.RUNNING diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 259c74ff25fa..d3019f100774 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -21,18 +21,16 @@ package com.android.systemui.keyguard.ui.viewmodel import android.view.View 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.Flags as AConfigFlags +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.FakeFeatureFlagsClassicModule import com.android.systemui.flags.Flags @@ -46,6 +44,8 @@ import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.plugins.ClockController +import com.android.systemui.runCurrent +import com.android.systemui.runTest import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScreenOffAnimationController @@ -64,7 +64,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow 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 @@ -109,10 +108,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() { mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - val featureFlags = - FakeFeatureFlagsClassic().apply { - set(Flags.FACE_AUTH_REFACTOR, true) - } + val featureFlags = FakeFeatureFlagsClassic().apply { set(Flags.FACE_AUTH_REFACTOR, true) } val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags) keyguardInteractor = withDeps.keyguardInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..c50be04e8a9c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule +import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.collectLastValue +import com.android.systemui.collectValues +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR +import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.runCurrent +import com.android.systemui.runTest +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.google.common.collect.Range +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenToAodTransitionViewModelTest : SysuiTestCase() { + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToAodTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val deviceEntryRepository: FakeDeviceEntryRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + val fingerprintPropertyRepository: FakeFingerprintPropertyRepository + val biometricSettingsRepository: FakeBiometricSettingsRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private fun TestComponent.shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } + + private val testComponent: TestComponent = + DaggerLockscreenToAodTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(FACE_AUTH_REFACTOR, true) + set(FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) + + @Test + fun backgroundViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.3f)) + assertThat(actual).isIn(Range.closed(.1f, .9f)) + + // finish fading out before the end of the full transition + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) + } + + @Test + fun backgroundViewAlpha_shadeExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + shadeExpanded(true) + runCurrent() + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.3f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled_shadeNotExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + shadeExpanded(false) + runCurrent() + + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(.3f), + step(.7f), + step(1f), + ), + testScope = testScope, + ) + // immediately 1f + values.forEach { assertThat(it).isEqualTo(1f) } + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled_shadeExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + shadeExpanded(true) + runCurrent() + + // fade in + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.3f)) + assertThat(actual).isIn(Range.closed(.1f, .9f)) + + // finish fading in before the end of the full transition + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFp_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsRearFps() + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.1f)) + assertThat(actual).isIn(Range.closed(.1f, .9f)) + + // finish fading out before the end of the full transition + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFp_shadeExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsRearFps() + shadeExpanded(true) + runCurrent() + + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(.3f), + step(.7f), + step(1f), + ), + testScope = testScope, + ) + // immediately 0f + values.forEach { assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = value, + transitionState = state, + ownerName = "LockscreenToAodTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index 89a1d2b3011d..26704da1496f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -18,88 +18,171 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue +import com.android.systemui.collectValues +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.runCurrent +import com.android.systemui.runTest +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import org.junit.Before +import dagger.BindsInstance +import dagger.Component import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { - private lateinit var underTest: LockscreenToDreamingTransitionViewModel - private lateinit var repository: FakeKeyguardTransitionRepository - - @Before - fun setUp() { - repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = LockscreenToDreamingTransitionViewModel(interactor) + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToDreamingTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } } + private fun TestComponent.shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } + + private val testComponent: TestComponent = + DaggerLockscreenToDreamingTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, true) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) @Test fun lockscreenFadeOut() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - - val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) - - // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.1f)) - repository.sendTransitionStep(step(0.2f)) - repository.sendTransitionStep(step(0.3f)) - // ...up to here - repository.sendTransitionStep(step(1f)) + testComponent.runTest { + val values by collectValues(underTest.lockscreenAlpha) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.1f), + step(.2f), + step(.3f), // ...up to here + step(1f), + ), + testScope = testScope, + ) // Only three values should be present, since the dream overlay runs for a small // fraction of the overall animation time assertThat(values.size).isEqualTo(5) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } - - job.cancel() } @Test fun lockscreenTranslationY() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - + testComponent.runTest { val pixels = 100 - val job = - underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + val values by collectValues(underTest.lockscreenTranslationY(pixels)) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.5f)) - repository.sendTransitionStep(step(1f)) - // And a final reset event on FINISHED - repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.3f), + step(.5f), + step(1f), + step(1f, TransitionState.FINISHED), // Final reset event on FINISHED + ), + testScope = testScope, + ) assertThat(values.size).isEqualTo(6) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } // Validate finished value assertThat(values[5]).isEqualTo(0f) + } - job.cancel() + @Test + fun deviceEntryParentViewAlpha_shadeExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + shadeExpanded(true) + runCurrent() + + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(0f), + step(.3f), + step(.5f), + step(1f), + step(1f, TransitionState.FINISHED), + ), + testScope = testScope, + ) + + // immediately 0f + values.forEach { assertThat(it).isEqualTo(0f) } + } + + @Test + fun deviceEntryParentViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.1f)) + assertThat(actual).isIn(Range.open(.1f, .9f)) + + // alpha is 1f before the full transition starts ending + repository.sendTransitionStep(step(0.8f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) } private fun step( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt new file mode 100644 index 000000000000..1494c92cdb06 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenToGoneTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: LockscreenToGoneTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = + LockscreenToGoneTransitionViewModel( + interactor, + ) + } + + @Test + fun deviceEntryParentViewHides() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + value = value, + transitionState = state, + ownerName = "LockscreenToGoneTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index 41f8856d5ca2..ff3135a6ad98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -18,109 +18,185 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue +import com.android.systemui.collectValues +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.runCurrent +import com.android.systemui.runTest +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import org.junit.Before +import dagger.BindsInstance +import dagger.Component import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { - private lateinit var underTest: LockscreenToOccludedTransitionViewModel - private lateinit var repository: FakeKeyguardTransitionRepository - - @Before - fun setUp() { - repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = LockscreenToOccludedTransitionViewModel(interactor) + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToOccludedTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } } - @Test - fun lockscreenFadeOut() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - - val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) + private fun TestComponent.shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } - // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.1f)) - repository.sendTransitionStep(step(0.4f)) - repository.sendTransitionStep(step(0.7f)) - // ...up to here - repository.sendTransitionStep(step(1f)) + private val testComponent: TestComponent = + DaggerLockscreenToOccludedTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, true) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) + @Test + fun lockscreenFadeOut() = + testComponent.runTest { + val values by collectValues(underTest.lockscreenAlpha) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.1f), + step(.4f), + step(.7f), // ...up to here + step(1f), + ), + testScope = testScope, + ) // Only 3 values should be present, since the dream overlay runs for a small fraction // of the overall animation time assertThat(values.size).isEqualTo(5) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } - - job.cancel() } @Test fun lockscreenTranslationY() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - + testComponent.runTest { val pixels = 100 - val job = - underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - - // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.5f)) - repository.sendTransitionStep(step(1f)) - // ...up to here - + val values by collectValues(underTest.lockscreenTranslationY(pixels)) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.3f), + step(.5f), + step(1f), // ...up to here + ), + testScope = testScope, + ) assertThat(values.size).isEqualTo(5) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } - - job.cancel() } @Test fun lockscreenTranslationYIsCanceled() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - + testComponent.runTest { val pixels = 100 - val job = - underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.3f, TransitionState.CANCELED)) - + val values by collectValues(underTest.lockscreenTranslationY(pixels)) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(0f), + step(.3f), + step(0.3f, TransitionState.CANCELED), + ), + testScope = testScope, + ) assertThat(values.size).isEqualTo(4) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } // Cancel will reset the translation assertThat(values[3]).isEqualTo(0) + } + + @Test + fun deviceEntryParentViewAlpha_shadeExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + shadeExpanded(true) + runCurrent() + + // immediately 0f + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(.5f), + step(1f, TransitionState.FINISHED) + ), + testScope = testScope, + ) + + values.forEach { assertThat(it).isEqualTo(0f) } + } + + @Test + fun deviceEntryParentViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.2f)) + assertThat(actual).isIn(Range.open(.1f, .9f)) + + // alpha is 1f before the full transition starts ending + repository.sendTransitionStep(step(0.8f)) + assertThat(actual).isEqualTo(0f) - job.cancel() + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) } private fun step( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt new file mode 100644 index 000000000000..8afd8e4fd425 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule +import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.runCurrent +import com.android.systemui.runTest +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.google.common.collect.Range +import com.google.common.truth.Truth +import dagger.BindsInstance +import dagger.Component +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToPrimaryBouncerTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private fun TestComponent.shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } + + private val testComponent: TestComponent = + DaggerLockscreenToPrimaryBouncerTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, true) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) + + @Test + fun deviceEntryParentViewAlpha_shadeExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(true) + runCurrent() + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.2f)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(0.8f)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.1f)) + runCurrent() + Truth.assertThat(actual).isIn(Range.open(.1f, .9f)) + + // alpha is 1f before the full transition starts ending + repository.sendTransitionStep(step(0.8f)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING, + ): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + value = value, + transitionState = state, + ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..0eb8ff6ba966 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class OccludedToAodTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: OccludedToAodTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + + underTest = + OccludedToAodTransitionViewModel( + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) + } + + @Test + fun deviceEntryBackgroundViewAlpha() = runTest { + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // immediately 1f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFpEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // no updates + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + @Test + fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // no updates + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.AOD, + value = value, + transitionState = state, + ownerName = "OccludedToAodTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index ec95cb8c43f1..d0772270ed5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt @@ -19,6 +19,10 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState @@ -26,6 +30,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -35,22 +40,35 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: OccludedToLockscreenTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = OccludedToLockscreenTransitionViewModel(interactor) + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + underTest = + OccludedToLockscreenTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) } @Test @@ -113,6 +131,78 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun deviceEntryParentViewFadeIn() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.deviceEntryParentViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + // Should start running here... + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + // ...up to here + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + + job.cancel() + } + + @Test + fun deviceEntryBackgroundViewShows() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + + values.forEach { assertThat(it).isEqualTo(1f) } + + job.cancel() + } + + @Test + fun deviceEntryBackgroundView_noUdfpsEnrolled_noUpdates() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values).isEmpty() // no updates + + job.cancel() + } + private fun step( value: Float, state: TransitionState = TransitionState.RUNNING diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..350b31008478 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class PrimaryBouncerToAodTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: PrimaryBouncerToAodTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = + PrimaryBouncerToAodTransitionViewModel( + interactor, + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) + } + + @Test + fun deviceEntryBackgroundViewAlpha() = runTest { + fingerprintPropertyRepository.supportsUdfps() + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled_fadeIn() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(.75f)) + repository.sendTransitionStep(step(1f)) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFpEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + @Test + fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.75f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.AOD, + value = value, + transitionState = state, + ownerName = "PrimaryBouncerToAodTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..24e4920c66d6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: PrimaryBouncerToLockscreenTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + + underTest = + PrimaryBouncerToLockscreenTransitionViewModel( + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) + } + + @Test + fun deviceEntryParentViewAlpha() = runTest { + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // immediately 1f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryBackgroundViewAlpha_udfpsEnrolled_show() = runTest { + fingerprintPropertyRepository.supportsUdfps() + val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 1f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(0.1f)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.3f)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.5f)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(bgViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryBackgroundViewAlpha_rearFpEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(.75f)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(bgViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ownerName = "PrimaryBouncerToLockscreenTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/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/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt index 3b6bfeeb4ca2..3808c7ee926b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt @@ -32,6 +32,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.res.R import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test @@ -67,17 +72,24 @@ class BluetoothTileDialogTest : SysuiTestCase() { private val fakeSystemClock = FakeSystemClock() + private lateinit var scheduler: TestCoroutineScheduler + private lateinit var dispatcher: CoroutineDispatcher + private lateinit var testScope: TestScope private lateinit var icon: Pair<Drawable, String> private lateinit var bluetoothTileDialog: BluetoothTileDialog private lateinit var deviceItem: DeviceItem @Before fun setUp() { + scheduler = TestCoroutineScheduler() + dispatcher = UnconfinedTestDispatcher(scheduler) + testScope = TestScope(dispatcher) bluetoothTileDialog = BluetoothTileDialog( ENABLED, subtitleResId, bluetoothTileDialogCallback, + dispatcher, fakeSystemClock, uiEventLogger, logger, @@ -111,29 +123,33 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Test fun testShowDialog_displayBluetoothDevice() { - bluetoothTileDialog = - BluetoothTileDialog( - ENABLED, - subtitleResId, - bluetoothTileDialogCallback, - fakeSystemClock, - uiEventLogger, - logger, - mContext + testScope.runTest { + bluetoothTileDialog = + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + dispatcher, + fakeSystemClock, + uiEventLogger, + logger, + mContext + ) + bluetoothTileDialog.show() + fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE) + bluetoothTileDialog.onDeviceItemUpdated( + listOf(deviceItem), + showSeeAll = false, + showPairNewDevice = false ) - bluetoothTileDialog.show() - bluetoothTileDialog.onDeviceItemUpdated( - listOf(deviceItem), - showSeeAll = false, - showPairNewDevice = false - ) - val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) - val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter - assertThat(adapter.itemCount).isEqualTo(1) - assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME) - assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY) - assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon) + val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) + val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter + assertThat(adapter.itemCount).isEqualTo(1) + assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME) + assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY) + assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon) + } } @Test @@ -147,6 +163,7 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + dispatcher, fakeSystemClock, uiEventLogger, logger, @@ -173,6 +190,7 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + dispatcher, fakeSystemClock, uiEventLogger, logger, @@ -190,33 +208,37 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Test fun testOnDeviceUpdated_hideSeeAll_showPairNew() { - bluetoothTileDialog = - BluetoothTileDialog( - ENABLED, - subtitleResId, - bluetoothTileDialogCallback, - fakeSystemClock, - uiEventLogger, - logger, - mContext + testScope.runTest { + bluetoothTileDialog = + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + dispatcher, + fakeSystemClock, + uiEventLogger, + logger, + mContext + ) + bluetoothTileDialog.show() + fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE) + bluetoothTileDialog.onDeviceItemUpdated( + listOf(deviceItem), + showSeeAll = false, + showPairNewDevice = true ) - bluetoothTileDialog.show() - bluetoothTileDialog.onDeviceItemUpdated( - listOf(deviceItem), - showSeeAll = false, - showPairNewDevice = true - ) - - val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group) - val pairNewLayout = - bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group) - val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) - val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter - assertThat(seeAllLayout).isNotNull() - assertThat(seeAllLayout.visibility).isEqualTo(GONE) - assertThat(pairNewLayout).isNotNull() - assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE) - assertThat(adapter.itemCount).isEqualTo(1) + val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group) + val pairNewLayout = + bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group) + val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) + val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter + + assertThat(seeAllLayout).isNotNull() + assertThat(seeAllLayout.visibility).isEqualTo(GONE) + assertThat(pairNewLayout).isNotNull() + assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE) + assertThat(adapter.itemCount).isEqualTo(1) + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt deleted file mode 100644 index d3b7daad792e..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.tiles.viewmodel - -import android.os.UserHandle -import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.MediumTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.classifier.FalsingManagerFake -import com.android.systemui.common.shared.model.ContentDescription -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics -import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor -import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor -import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor -import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper -import com.android.systemui.qs.tiles.base.logging.QSTileLogger -import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl -import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations - -// TODO(b/299909368): Add more tests -@MediumTest -@RunWith(AndroidJUnit4::class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { - - @Mock private lateinit var qsTileLogger: QSTileLogger - @Mock private lateinit var qsTileAnalytics: QSTileAnalytics - - private val fakeUserRepository = FakeUserRepository() - private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>() - private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>() - private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor() - private val fakeFalsingManager = FalsingManagerFake() - - private val testCoroutineDispatcher = StandardTestDispatcher() - private val testScope = TestScope(testCoroutineDispatcher) - - private lateinit var underTest: QSTileViewModel - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - underTest = createViewModel(testScope) - } - - @Test - fun testDoesntListenStateUntilCreated() = - testScope.runTest { - assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty() - - assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty() - - underTest.state.launchIn(backgroundScope) - runCurrent() - - assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty() - assertThat(fakeQSTileDataInteractor.dataRequests.first()) - .isEqualTo(FakeQSTileDataInteractor.DataRequest(UserHandle.of(0))) - } - - private fun createViewModel( - scope: TestScope, - config: QSTileConfig = TEST_QS_TILE_CONFIG, - ): QSTileViewModel = - QSTileViewModelImpl( - config, - { fakeQSTileUserActionInteractor }, - { fakeQSTileDataInteractor }, - { - object : QSTileDataToStateMapper<Any> { - override fun map(config: QSTileConfig, data: Any): QSTileState = - QSTileState.build( - { Icon.Resource(0, ContentDescription.Resource(0)) }, - "" - ) {} - } - }, - fakeDisabledByPolicyInteractor, - fakeUserRepository, - fakeFalsingManager, - qsTileAnalytics, - qsTileLogger, - FakeSystemClock(), - testCoroutineDispatcher, - scope.backgroundScope, - ) - - private companion object { - - val TEST_QS_TILE_CONFIG = QSTileConfigTestBuilder.build {} - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt new file mode 100644 index 000000000000..3a0ebdbd6a17 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@MediumTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class QSTileViewModelTest : SysuiTestCase() { + + @Mock private lateinit var qsTileLogger: QSTileLogger + @Mock private lateinit var qsTileAnalytics: QSTileAnalytics + + private val tileConfig = + QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") } + + private val userRepository = FakeUserRepository() + private val tileDataInteractor = FakeQSTileDataInteractor<String>() + private val tileUserActionInteractor = FakeQSTileUserActionInteractor<String>() + private val disabledByPolicyInteractor = FakeDisabledByPolicyInteractor() + private val falsingManager = FalsingManagerFake() + + private val testCoroutineDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testCoroutineDispatcher) + + private lateinit var underTest: QSTileViewModel + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = createViewModel(testScope) + } + + @Test + fun stateReceivedForTheData() = + testScope.runTest { + val testTileData = "test_tile_data" + val states = collectValues(underTest.state) + runCurrent() + + tileDataInteractor.emitData(testTileData) + runCurrent() + + assertThat(states()).isNotEmpty() + assertThat(states().first().label).isEqualTo(testTileData) + verify(qsTileLogger).logInitialRequest(eq(tileConfig.tileSpec)) + } + + @Test + fun doesntListenDataIfStateIsntListened() = + testScope.runTest { + assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(0) + + underTest.state.launchIn(backgroundScope) + runCurrent() + + assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(1) + } + + @Test + fun doesntListenAvailabilityIfAvailabilityIsntListened() = + testScope.runTest { + assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(0) + + underTest.isAvailable.launchIn(backgroundScope) + runCurrent() + + assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(1) + } + + @Test + fun doesntListedDataAfterDestroy() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + underTest.isAvailable.launchIn(backgroundScope) + runCurrent() + + underTest.destroy() + runCurrent() + + assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(0) + assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(0) + } + + @Test + fun forceUpdateTriggersData() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + runCurrent() + + underTest.forceUpdate() + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isInstanceOf(DataUpdateTrigger.ForceUpdate::class.java) + verify(qsTileLogger).logForceUpdate(eq(tileConfig.tileSpec)) + } + + @Test + fun userChangeUpdatesData() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + runCurrent() + + underTest.onUserChanged(USER) + runCurrent() + + assertThat(tileDataInteractor.dataRequests.last()) + .isEqualTo(FakeQSTileDataInteractor.DataRequest(USER)) + } + + @Test + fun userChangeUpdatesAvailability() = + testScope.runTest { + underTest.isAvailable.launchIn(backgroundScope) + runCurrent() + + underTest.onUserChanged(USER) + runCurrent() + + assertThat(tileDataInteractor.availabilityRequests.last()) + .isEqualTo(FakeQSTileDataInteractor.AvailabilityRequest(USER)) + } + + private fun createViewModel( + scope: TestScope, + config: QSTileConfig = tileConfig, + ): QSTileViewModel = + QSTileViewModelImpl( + config, + { tileUserActionInteractor }, + { tileDataInteractor }, + { + object : QSTileDataToStateMapper<String> { + override fun map(config: QSTileConfig, data: String): QSTileState = + QSTileState.build( + { Icon.Resource(0, ContentDescription.Resource(0)) }, + data + ) {} + } + }, + disabledByPolicyInteractor, + userRepository, + falsingManager, + qsTileAnalytics, + qsTileLogger, + FakeSystemClock(), + testCoroutineDispatcher, + scope.backgroundScope, + ) + + private companion object { + + val USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt new file mode 100644 index 000000000000..ea8acc714f3a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import androidx.test.filters.MediumTest +import com.android.settingslib.RestrictedLockUtils +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +/** Tests all possible [QSTileUserAction]s. If you need */ +@MediumTest +@RunWith(Parameterized::class) +@OptIn(ExperimentalCoroutinesApi::class) +class QSTileViewModelUserInputTest : SysuiTestCase() { + + @Mock private lateinit var qsTileLogger: QSTileLogger + @Mock private lateinit var qsTileAnalytics: QSTileAnalytics + + @Parameter lateinit var userAction: QSTileUserAction + + private val tileConfig = + QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") } + + private val userRepository = FakeUserRepository() + private val tileDataInteractor = FakeQSTileDataInteractor<String>() + private val tileUserActionInteractor = FakeQSTileUserActionInteractor<String>() + private val disabledByPolicyInteractor = FakeDisabledByPolicyInteractor() + private val falsingManager = FalsingManagerFake() + + private val testCoroutineDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testCoroutineDispatcher) + + private lateinit var underTest: QSTileViewModel + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = createViewModel(testScope) + } + + @Test + fun userInputTriggersData() = + testScope.runTest { + tileDataInteractor.emitData("initial_data") + underTest.state.launchIn(backgroundScope) + runCurrent() + + underTest.onActionPerformed(userAction) + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isInstanceOf(DataUpdateTrigger.UserInput::class.java) + verify(qsTileLogger) + .logUserAction(eq(userAction), eq(tileConfig.tileSpec), eq(true), eq(true)) + verify(qsTileLogger) + .logUserActionPipeline( + eq(tileConfig.tileSpec), + eq(userAction), + any(), + eq("initial_data") + ) + verify(qsTileAnalytics).trackUserAction(eq(tileConfig), eq(userAction)) + } + + @Test + fun disabledByPolicyUserInputIsSkipped() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + disabledByPolicyInteractor.policyResult = + DisabledByPolicyInteractor.PolicyResult.TileDisabled( + RestrictedLockUtils.EnforcedAdmin() + ) + runCurrent() + + underTest.onActionPerformed(userAction) + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java) + verify(qsTileLogger) + .logUserActionRejectedByPolicy(eq(userAction), eq(tileConfig.tileSpec)) + verify(qsTileAnalytics, never()).trackUserAction(any(), any()) + } + + @Test + fun falsedUserInputIsSkipped() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + falsingManager.setFalseLongTap(true) + falsingManager.setFalseTap(true) + runCurrent() + + underTest.onActionPerformed(userAction) + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java) + verify(qsTileLogger) + .logUserActionRejectedByFalsing(eq(userAction), eq(tileConfig.tileSpec)) + verify(qsTileAnalytics, never()).trackUserAction(any(), any()) + } + + @Test + fun userInputIsThrottled() = + testScope.runTest { + val inputCount = 100 + underTest.state.launchIn(backgroundScope) + + repeat(inputCount) { underTest.onActionPerformed(userAction) } + runCurrent() + + assertThat(tileDataInteractor.triggers.size).isLessThan(inputCount) + } + + private fun createViewModel(scope: TestScope): QSTileViewModel = + QSTileViewModelImpl( + tileConfig, + { tileUserActionInteractor }, + { tileDataInteractor }, + { + object : QSTileDataToStateMapper<String> { + override fun map(config: QSTileConfig, data: String): QSTileState = + QSTileState.build( + { Icon.Resource(0, ContentDescription.Resource(0)) }, + data + ) {} + } + }, + disabledByPolicyInteractor, + userRepository, + falsingManager, + qsTileAnalytics, + qsTileLogger, + FakeSystemClock(), + testCoroutineDispatcher, + scope.backgroundScope, + ) + + companion object { + + @JvmStatic + @Parameterized.Parameters + fun data(): Iterable<QSTileUserAction> = + listOf( + QSTileUserAction.Click(null), + QSTileUserAction.LongClick(null), + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/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 04a05e27473d..f1429b5dd7b3 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,7 @@ 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.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; @@ -371,11 +372,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.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(); @@ -422,7 +425,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mDozeParameters, mScreenOffAnimationController, mKeyguardLogger, - mFeatureFlags, mInteractionJankMonitor, mKeyguardInteractor, mKeyguardTransitionInteractor, @@ -769,7 +771,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/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..d050c856c2e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -355,7 +355,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mAccessibilityManager, mLockscreenGestureLogger, mMetricsLogger, - mFeatureFlags, mInteractionJankMonitor, mShadeLogger, mDumpManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index ff7443f10bf3..32daccb28cce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -22,13 +22,11 @@ import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS 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.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FakeFeatureFlagsClassicModule @@ -45,6 +43,8 @@ import com.android.systemui.power.data.repository.FakePowerRepository 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.runCurrent +import com.android.systemui.runTest import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey 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..fdbdc7963b74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -5,8 +5,8 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest -import com.android.SysUITestModule -import com.android.TestMocksModule +import com.android.systemui.SysUITestModule +import com.android.systemui.TestMocksModule import com.android.systemui.ExpandHelper import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollectorFake @@ -607,9 +607,9 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Component.Factory interface Factory { fun create( - @BindsInstance test: SysuiTestCase, - featureFlags: FakeFeatureFlagsClassicModule, - mocks: TestMocksModule, + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, ): TestComponent } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt index d47993793fc0..f3094cdd4faf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt @@ -17,13 +17,13 @@ package com.android.systemui.statusbar.notification.data.repository 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.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.collectLastValue import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.runCurrent +import com.android.systemui.runTest import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt index 707026e42009..b7750795fe71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.domain.interactor import android.app.StatusBarManager import androidx.test.filters.SmallTest -import com.android.SysUITestComponent -import com.android.SysUITestModule +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt index bb6f1b6a2850..bb3113a72e92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt @@ -14,20 +14,17 @@ package com.android.systemui.statusbar.notification.domain.interactor 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.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.collectLastValue import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.runCurrent +import com.android.systemui.runTest import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Test @SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt index 05deb1cc75c7..034103598bb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt @@ -17,14 +17,14 @@ package com.android.systemui.statusbar.notification.icon.domain.interactor import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.SysUITestComponent -import com.android.SysUITestModule -import com.android.TestMocksModule -import com.android.collectLastValue -import com.android.runTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository +import com.android.systemui.runTest import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt index c2c33de015ef..68761ef8139d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt @@ -18,14 +18,12 @@ package com.android.systemui.statusbar.notification.icon.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.SysUITestComponent -import com.android.SysUITestModule -import com.android.TestMocksModule -import com.android.collectLastValue -import com.android.runCurrent -import com.android.runTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule +import com.android.systemui.collectLastValue import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FakeFeatureFlagsClassicModule import com.android.systemui.flags.Flags @@ -39,6 +37,8 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.runCurrent +import com.android.systemui.runTest import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index 1a04a3ea291a..c2a1519f85dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -20,14 +20,12 @@ import android.graphics.Rect import android.graphics.drawable.Icon import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.SysUITestComponent -import com.android.SysUITestModule -import com.android.TestMocksModule -import com.android.collectLastValue -import com.android.runCurrent -import com.android.runTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule +import com.android.systemui.collectLastValue import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FakeFeatureFlagsClassicModule import com.android.systemui.flags.Flags @@ -42,6 +40,8 @@ import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.runCurrent +import com.android.systemui.runTest import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore @@ -389,4 +389,31 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1") assertThat(isolatedIcon?.isAnimating).isFalse() } + + @Test + fun isolatedIcon_updateWhenIconDataChanges() = + testComponent.runTest { + val icon: Icon = mock() + val isolatedIcon by collectLastValue(underTest.isolatedIcon) + runCurrent() + + headsUpViewStateRepository.isolatedNotification.value = "notif1" + runCurrent() + + activeNotificationsRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + groupKey = "group", + statusBarIcon = icon + ) + ) + } + .build() + runCurrent() + + assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1") + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt index 1d2055ec2e30..e1581eade4ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt @@ -44,7 +44,7 @@ class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecision statusBarStateController, keyguardStateController, headsUpManager, - logger, + oldLogger, mainHandler, flags, keyguardNotificationVisibilityProvider, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index 722b1704bb18..1064475c744c 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 @@ -37,7 +37,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro headsUpManager, keyguardNotificationVisibilityProvider, keyguardStateController, - logger, + newLogger, mainHandler, powerManager, statusBarStateController, @@ -222,14 +222,14 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro private class TestCondition( types: Set<VisualInterruptionType>, val onShouldSuppress: () -> Boolean - ) : VisualInterruptionCondition(types = types, reason = "") { + ) : VisualInterruptionCondition(types = types, reason = "test condition") { override fun shouldSuppress(): Boolean = onShouldSuppress() } private class TestFilter( types: Set<VisualInterruptionType>, val onShouldSuppress: (NotificationEntry) -> Boolean = { true } - ) : VisualInterruptionFilter(types = types, reason = "") { + ) : VisualInterruptionFilter(types = types, reason = "test filter") { override fun shouldSuppress(entry: NotificationEntry) = onShouldSuppress(entry) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt index 0f298365bbc5..5e811561682e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt @@ -20,7 +20,9 @@ import android.app.ActivityManager import android.app.Notification import android.app.Notification.BubbleMetadata import android.app.Notification.FLAG_BUBBLE +import android.app.Notification.FLAG_FOREGROUND_SERVICE import android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED +import android.app.Notification.FLAG_USER_INITIATED_JOB import android.app.Notification.GROUP_ALERT_ALL import android.app.Notification.GROUP_ALERT_CHILDREN import android.app.Notification.GROUP_ALERT_SUMMARY @@ -47,6 +49,9 @@ import android.provider.Settings.Global.HEADS_UP_OFF import android.provider.Settings.Global.HEADS_UP_ON import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.statusbar.FakeStatusBarStateController @@ -76,19 +81,35 @@ import org.junit.Test import org.mockito.Mockito.`when` as whenever abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { + private val fakeLogBuffer = + LogBuffer( + name = "FakeLog", + maxSize = 1, + logcatEchoTracker = + object : LogcatEchoTracker { + override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = + true + + override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true + }, + systrace = false + ) + private val leakCheck = LeakCheckedTest.SysuiLeakCheck() protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context) protected val batteryController = FakeBatteryController(leakCheck) protected val deviceProvisionedController = FakeDeviceProvisionedController() protected val flags: NotifPipelineFlags = mock() - protected val globalSettings = FakeGlobalSettings() + protected val globalSettings = + FakeGlobalSettings().also { it.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) } protected val headsUpManager: HeadsUpManager = mock() protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider = mock() protected val keyguardStateController = FakeKeyguardStateController(leakCheck) - protected val logger: NotificationInterruptLogger = mock() protected val mainHandler = FakeHandler(Looper.getMainLooper()) + protected val newLogger = VisualInterruptionDecisionLogger(fakeLogBuffer) + protected val oldLogger = NotificationInterruptLogger(fakeLogBuffer) protected val powerManager: PowerManager = mock() protected val statusBarStateController = FakeStatusBarStateController() protected val systemClock = FakeSystemClock() @@ -116,14 +137,9 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { @Before fun setUp() { - globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) - val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0) userTracker.set(listOf(user), /* currentUserIndex = */ 0) - whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any())) - .thenReturn(false) - provider.start() } @@ -203,24 +219,64 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldPeek_notQuiteOldEnoughWhen() { + fun testShouldPeek_oldWhen_now() { + ensurePeekState() + assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(0) }) + } + + @Test + fun testShouldPeek_oldWhen_notOldEnough() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS - 1) }) } @Test - fun testShouldPeek_zeroWhen() { + fun testShouldPeek_oldWhen_zeroWhen() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = 0L }) } @Test - fun testShouldPeek_oldWhenButFsi() { + fun testShouldPeek_oldWhen_negativeWhen() { + ensurePeekState() + assertShouldHeadsUp(buildPeekEntry { whenMs = -1L }) + } + + @Test + fun testShouldPeek_oldWhen_fullScreenIntent() { ensurePeekState() assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }) } @Test + fun testShouldPeek_oldWhen_foregroundService() { + ensurePeekState() + assertShouldHeadsUp( + buildPeekEntry { + whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) + isForegroundService = true + } + ) + } + + @Test + fun testShouldPeek_oldWhen_userInitiatedJob() { + ensurePeekState() + assertShouldHeadsUp( + buildPeekEntry { + whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) + isUserInitiatedJob = true + } + ) + } + + @Test + fun testShouldNotPeek_hiddenOnKeyguard() { + ensurePeekState({ keyguardShouldHideNotification = true }) + assertShouldNotHeadsUp(buildPeekEntry()) + } + + @Test fun testShouldPeek_defaultLegacySuppressor() { ensurePeekState() withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPeekEntry()) } @@ -257,36 +313,6 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldPulse_defaultLegacySuppressor() { - ensurePulseState() - withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) } - } - - @Test - fun testShouldNotPulse_legacySuppressInterruptions() { - ensurePulseState() - withLegacySuppressor(alwaysSuppressesInterruptions) { - assertShouldNotHeadsUp(buildPulseEntry()) - } - } - - @Test - fun testShouldPulse_legacySuppressAwakeInterruptions() { - ensurePulseState() - withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { - assertShouldHeadsUp(buildPulseEntry()) - } - } - - @Test - fun testShouldPulse_legacySuppressAwakeHeadsUp() { - ensurePulseState() - withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { - assertShouldHeadsUp(buildPulseEntry()) - } - } - - @Test fun testShouldNotPulse_disabled() { ensurePulseState { pulseOnNotificationsEnabled = false } assertShouldNotHeadsUp(buildPulseEntry()) @@ -318,74 +344,66 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { assertShouldNotHeadsUp(buildPulseEntry { importance = IMPORTANCE_LOW }) } - private fun withPeekAndPulseEntry( - extendEntry: EntryBuilder.() -> Unit, - block: (NotificationEntry) -> Unit - ) { - ensurePeekState() - block(buildPeekEntry(extendEntry)) + @Test + fun testShouldNotPulse_hiddenOnKeyguard() { + ensurePulseState({ keyguardShouldHideNotification = true }) + assertShouldNotHeadsUp(buildPulseEntry()) + } + @Test + fun testShouldPulse_defaultLegacySuppressor() { ensurePulseState() - block(buildPulseEntry(extendEntry)) + withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) } } @Test - fun testShouldHeadsUp_groupedSummaryNotif_groupAlertAll() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_ALL - }) { - assertShouldHeadsUp(it) + fun testShouldNotPulse_legacySuppressInterruptions() { + ensurePulseState() + withLegacySuppressor(alwaysSuppressesInterruptions) { + assertShouldNotHeadsUp(buildPulseEntry()) } } @Test - fun testShouldHeadsUp_groupedSummaryNotif_groupAlertSummary() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_SUMMARY - }) { - assertShouldHeadsUp(it) + fun testShouldPulse_legacySuppressAwakeInterruptions() { + ensurePulseState() + withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { + assertShouldHeadsUp(buildPulseEntry()) } } @Test - fun testShouldNotHeadsUp_groupedSummaryNotif_groupAlertChildren() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_CHILDREN - }) { - assertShouldNotHeadsUp(it) + fun testShouldPulse_legacySuppressAwakeHeadsUp() { + ensurePulseState() + withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { + assertShouldHeadsUp(buildPulseEntry()) } } - @Test - fun testShouldHeadsUp_ungroupedSummaryNotif_groupAlertChildren() { - withPeekAndPulseEntry({ - isGrouped = false - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_CHILDREN - }) { - assertShouldHeadsUp(it) - } + private fun withPeekAndPulseEntry( + extendEntry: EntryBuilder.() -> Unit, + block: (NotificationEntry) -> Unit + ) { + ensurePeekState() + block(buildPeekEntry(extendEntry)) + + ensurePulseState() + block(buildPulseEntry(extendEntry)) } @Test - fun testShouldHeadsUp_groupedChildNotif_groupAlertAll() { + fun testShouldNotHeadsUp_suppressiveGroupAlertBehavior() { withPeekAndPulseEntry({ isGrouped = true isGroupSummary = false - groupAlertBehavior = GROUP_ALERT_ALL + groupAlertBehavior = GROUP_ALERT_SUMMARY }) { - assertShouldHeadsUp(it) + assertShouldNotHeadsUp(it) } } @Test - fun testShouldHeadsUp_groupedChildNotif_groupAlertChildren() { + fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notSuppressive() { withPeekAndPulseEntry({ isGrouped = true isGroupSummary = false @@ -396,18 +414,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldNotHeadsUp_groupedChildNotif_groupAlertSummary() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = false - groupAlertBehavior = GROUP_ALERT_SUMMARY - }) { - assertShouldNotHeadsUp(it) - } - } - - @Test - fun testShouldHeadsUp_ungroupedChildNotif_groupAlertSummary() { + fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notGrouped() { withPeekAndPulseEntry({ isGrouped = false isGroupSummary = false @@ -435,18 +442,41 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldNotBubble_notAllowed() { + fun testShouldBubble_suppressiveGroupAlertBehavior() { ensureBubbleState() - assertShouldNotBubble(buildBubbleEntry { canBubble = false }) + assertShouldBubble( + buildBubbleEntry { + isGrouped = true + isGroupSummary = false + groupAlertBehavior = GROUP_ALERT_SUMMARY + } + ) } @Test - fun testShouldNotBubble_noBubbleMetadata() { + fun testShouldNotBubble_notABubble() { + ensureBubbleState() + assertShouldNotBubble( + buildBubbleEntry { + isBubble = false + hasBubbleMetadata = false + } + ) + } + + @Test + fun testShouldNotBubble_missingBubbleMetadata() { ensureBubbleState() assertShouldNotBubble(buildBubbleEntry { hasBubbleMetadata = false }) } @Test + fun testShouldNotBubble_notAllowedToBubble() { + ensureBubbleState() + assertShouldNotBubble(buildBubbleEntry { canBubble = false }) + } + + @Test fun testShouldBubble_defaultLegacySuppressor() { ensureBubbleState() withLegacySuppressor(neverSuppresses) { assertShouldBubble(buildBubbleEntry()) } @@ -477,13 +507,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldNotAlert_hiddenOnKeyguard() { - ensurePeekState({ keyguardShouldHideNotification = true }) - assertShouldNotHeadsUp(buildPeekEntry()) - - ensurePulseState({ keyguardShouldHideNotification = true }) - assertShouldNotHeadsUp(buildPulseEntry()) - + fun testShouldNotBubble_hiddenOnKeyguard() { ensureBubbleState({ keyguardShouldHideNotification = true }) assertShouldNotBubble(buildBubbleEntry()) } @@ -855,12 +879,12 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } protected fun assertShouldHeadsUp(entry: NotificationEntry) = - provider.makeUnloggedHeadsUpDecision(entry).let { + provider.makeAndLogHeadsUpDecision(entry).let { assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt) } protected fun assertShouldNotHeadsUp(entry: NotificationEntry) = - provider.makeUnloggedHeadsUpDecision(entry).let { + provider.makeAndLogHeadsUpDecision(entry).let { assertFalse("unexpected unsuppressed HUN: ${it.logReason}", it.shouldInterrupt) } @@ -876,6 +900,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { protected fun assertShouldFsi(entry: NotificationEntry) = provider.makeUnloggedFullScreenIntentDecision(entry).let { + provider.logFullScreenIntentDecision(it) assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt) } @@ -884,10 +909,11 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { expectWouldInterruptWithoutDnd: Boolean? = null ) = provider.makeUnloggedFullScreenIntentDecision(entry).let { + provider.logFullScreenIntentDecision(it) assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt) if (expectWouldInterruptWithoutDnd != null) { assertEquals( - "unexpected unsuppressed-without-DND FSI: ${it.logReason}", + "unexpected wouldInterruptWithoutDnd for FSI: ${it.logReason}", expectWouldInterruptWithoutDnd, it.wouldInterruptWithoutDnd ) @@ -895,22 +921,35 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } protected class EntryBuilder(val context: Context) { - var importance = IMPORTANCE_DEFAULT - var suppressedVisualEffects: Int? = null - var whenMs: Long? = null - var visibilityOverride: Int? = null - var hasFsi = false - var canBubble: Boolean? = null - var isBubble = false - var hasBubbleMetadata = false + // Set on BubbleMetadata: var bubbleIsShortcut = false - var bubbleSuppressesNotification: Boolean? = null + var bubbleSuppressesNotification = false + + // Set on Notification.Builder: + var whenMs: Long? = null var isGrouped = false - var isGroupSummary: Boolean? = null + var isGroupSummary = false var groupAlertBehavior: Int? = null - var hasJustLaunchedFsi = false + var hasBubbleMetadata = false + var hasFsi = false + + // Set on Notification: + var isForegroundService = false + var isUserInitiatedJob = false + var isBubble = false var isStickyAndNotDemoted = false - var packageSuspended: Boolean? = null + + // Set on NotificationEntryBuilder: + var importance = IMPORTANCE_DEFAULT + var canBubble: Boolean? = null + + // Set on NotificationEntry: + var hasJustLaunchedFsi = false + + // Set on ModifiedRankingBuilder: + var packageSuspended = false + var visibilityOverride: Int? = null + var suppressedVisualEffects: Int? = null private fun buildBubbleMetadata(): BubbleMetadata { val builder = @@ -928,71 +967,87 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { ) } - bubbleSuppressesNotification?.let { builder.setSuppressNotification(it) } + if (bubbleSuppressesNotification) { + builder.setSuppressNotification(true) + } return builder.build() } fun build() = Notification.Builder(context, TEST_CHANNEL_ID) - .apply { - setContentTitle(TEST_CONTENT_TITLE) - setContentText(TEST_CONTENT_TEXT) + .also { nb -> + nb.setContentTitle(TEST_CONTENT_TITLE) + nb.setContentText(TEST_CONTENT_TEXT) - if (hasFsi) { - setFullScreenIntent(mock(), /* highPriority = */ true) + whenMs?.let { nb.setWhen(it) } + + if (isGrouped) { + nb.setGroup(TEST_GROUP_KEY) } - whenMs?.let { setWhen(it) } + if (isGroupSummary) { + nb.setGroupSummary(true) + } + + groupAlertBehavior?.let { nb.setGroupAlertBehavior(it) } if (hasBubbleMetadata) { - setBubbleMetadata(buildBubbleMetadata()) + nb.setBubbleMetadata(buildBubbleMetadata()) } - if (isGrouped) { - setGroup(TEST_GROUP_KEY) + if (hasFsi) { + nb.setFullScreenIntent(mock(), /* highPriority = */ true) } - - isGroupSummary?.let { setGroupSummary(it) } - - groupAlertBehavior?.let { setGroupAlertBehavior(it) } } .build() - .apply { + .also { n -> + if (isForegroundService) { + n.flags = n.flags or FLAG_FOREGROUND_SERVICE + } + + if (isUserInitiatedJob) { + n.flags = n.flags or FLAG_USER_INITIATED_JOB + } + if (isBubble) { - flags = flags or FLAG_BUBBLE + n.flags = n.flags or FLAG_BUBBLE } if (isStickyAndNotDemoted) { - flags = flags or FLAG_FSI_REQUESTED_BUT_DENIED + n.flags = n.flags or FLAG_FSI_REQUESTED_BUT_DENIED } } .let { NotificationEntryBuilder().setNotification(it) } - .apply { - setPkg(TEST_PACKAGE) - setOpPkg(TEST_PACKAGE) - setTag(TEST_TAG) - - setImportance(importance) - setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)) + .also { neb -> + neb.setPkg(TEST_PACKAGE) + neb.setOpPkg(TEST_PACKAGE) + neb.setTag(TEST_TAG) + + neb.setImportance(importance) + neb.setChannel( + NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance) + ) - canBubble?.let { setCanBubble(it) } + canBubble?.let { neb.setCanBubble(it) } } .build()!! - .also { + .also { ne -> if (hasJustLaunchedFsi) { - it.notifyFullScreenIntentLaunched() + ne.notifyFullScreenIntentLaunched() } if (isStickyAndNotDemoted) { - assertFalse(it.isDemoted) + assertFalse(ne.isDemoted) } - modifyRanking(it) - .apply { - suppressedVisualEffects?.let { setSuppressedVisualEffects(it) } - visibilityOverride?.let { setVisibilityOverride(it) } - packageSuspended?.let { setSuspended(it) } + modifyRanking(ne) + .also { mrb -> + if (packageSuspended) { + mrb.setSuspended(true) + } + visibilityOverride?.let { mrb.setVisibilityOverride(it) } + suppressedVisualEffects?.let { mrb.setSuppressedVisualEffects(it) } } .build() } @@ -1013,6 +1068,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } protected fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry { + isBubble = true canBubble = true hasBubbleMetadata = true run(block) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt index 7423c2decaec..917569ca787b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt @@ -19,16 +19,16 @@ package com.android.systemui.statusbar.notification.shelf.ui.viewmodel import android.os.PowerManager import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.SysUITestComponent -import com.android.SysUITestModule -import com.android.TestMocksModule -import com.android.collectLastValue -import com.android.runTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.runTest import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index db8f21714964..9c70c82cfa26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -19,13 +19,11 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.SysUITestComponent -import com.android.SysUITestModule -import com.android.TestMocksModule -import com.android.collectLastValue -import com.android.runCurrent -import com.android.runTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue import com.android.systemui.common.shared.model.SharedNotificationContainerPosition import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.dagger.SysUISingleton @@ -39,6 +37,8 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.res.R +import com.android.systemui.runCurrent +import com.android.systemui.runTest import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.user.domain.UserDomainLayerModule diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 9aafee4770de..6a0375d55a72 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import android.os.Handler import android.os.PowerManager import android.testing.AndroidTestingRunner @@ -82,14 +80,9 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var handler: Handler - private lateinit var featureFlags: FakeFeatureFlags - @Before fun setUp() { MockitoAnnotations.initMocks(this) - featureFlags = FakeFeatureFlags().apply { - set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false) - } controller = UnlockedScreenOffAnimationController( context, wakefulnessLifecycle, @@ -102,7 +95,6 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { interactionJankMonitor, powerManager, handler = handler, - featureFlags, ) controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index d1b9b8aae70e..0b87fe8da184 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; @@ -703,6 +704,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mKeyguardStateController, mShadeViewController, mStatusBarStateController, + mock(StatusBarIconViewBindingFailureTracker.class), mCommandQueue, mCarrierConfigTracker, new CollapsedStatusBarFragmentLogger( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/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/CoroutineTestScopeModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt index 360aa0f89a46..de310b49b8cc 100644 --- a/packages/SystemUI/tests/src/com/android/CoroutineTestScopeModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android +package com.android.systemui import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt index 97e43ad91f53..f2c32e3d7e35 100644 --- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt @@ -13,15 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android +package com.android.systemui import android.content.Context import android.content.res.Resources import android.testing.TestableContext import android.testing.TestableResources -import com.android.systemui.FakeSystemUiModule -import com.android.systemui.SysuiTestCase -import com.android.systemui.SysuiTestableContext import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.broadcast.FakeBroadcastDispatcher import com.android.systemui.coroutines.collectLastValue diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt index fd50f15dc5fc..37a4f6181921 100644 --- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android +package com.android.systemui import android.app.ActivityManager import android.app.admin.DevicePolicyManager @@ -24,7 +24,6 @@ import com.android.internal.logging.MetricsLogger import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardViewController -import com.android.systemui.GuestResumeSessionReceiver import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index 6e9363b744ab..af1930ef143e 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,10 @@ class FakeAuthenticationRepository( override val minPatternLength: Int = 4 + private val _isPinEnhancedPrivacyEnabled = MutableStateFlow(false) + override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + _isPinEnhancedPrivacyEnabled.asStateFlow() + private var failedAttemptCount = 0 private var throttlingEndTimestamp = 0L private var credentialOverride: List<Any>? = null @@ -138,6 +142,10 @@ class FakeAuthenticationRepository( } } + fun setPinEnhancedPrivacyEnabled(isEnabled: Boolean) { + _isPinEnhancedPrivacyEnabled.value = isEnabled + } + private fun getExpectedCredential(securityMode: SecurityMode): List<Any> { return when (val credentialType = getCurrentCredentialType(securityMode)) { LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt index 0c5e43809fab..005cac490d89 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt @@ -19,10 +19,15 @@ package com.android.systemui.biometrics.data.repository import android.hardware.biometrics.SensorLocationInternal import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -class FakeFingerprintPropertyRepository : FingerprintPropertyRepository { +@SysUISingleton +class FakeFingerprintPropertyRepository @Inject constructor() : FingerprintPropertyRepository { private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1) override val sensorId = _sensorId.asStateFlow() @@ -50,4 +55,29 @@ class FakeFingerprintPropertyRepository : FingerprintPropertyRepository { _sensorType.value = sensorType _sensorLocations.value = sensorLocations } + + /** setProperties as if the device supports UDFPS_OPTICAL. */ + fun supportsUdfps() { + setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.UDFPS_OPTICAL, + sensorLocations = emptyMap(), + ) + } + + /** setProperties as if the device supports the rear fingerprint sensor. */ + fun supportsRearFps() { + setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.REAR, + sensorLocations = emptyMap(), + ) + } +} + +@Module +interface FakeFingerprintPropertyRepositoryModule { + @Binds fun bindFake(fake: FakeFingerprintPropertyRepository): FingerprintPropertyRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt index 44286b715abb..8ff04a63802a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt @@ -15,17 +15,23 @@ package com.android.systemui.deviceentry.data +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepositoryModule import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepositoryModule import com.android.systemui.keyguard.data.repository.FakeTrustRepositoryModule import dagger.Module @Module( includes = [ + FakeBiometricSettingsRepositoryModule::class, FakeDeviceEntryRepositoryModule::class, - FakeTrustRepositoryModule::class, FakeDeviceEntryFaceAuthRepositoryModule::class, + FakeDeviceEntryFingerprintAuthRepositoryModule::class, + FakeFingerprintPropertyRepositoryModule::class, + FakeTrustRepositoryModule::class, ] ) object FakeDeviceEntryDataLayerModule diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt index 852611230623..df31a12b8415 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt @@ -18,13 +18,18 @@ package com.android.systemui.keyguard.data.repository import com.android.internal.widget.LockPatternUtils +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.AuthenticationFlags +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -class FakeBiometricSettingsRepository : BiometricSettingsRepository { +@SysUISingleton +class FakeBiometricSettingsRepository @Inject constructor() : BiometricSettingsRepository { private val _isFingerprintEnrolledAndEnabled = MutableStateFlow(false) override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> get() = _isFingerprintEnrolledAndEnabled @@ -97,3 +102,8 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { } } } + +@Module +interface FakeBiometricSettingsRepositoryModule { + @Binds fun bindFake(fake: FakeBiometricSettingsRepository): BiometricSettingsRepository +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt index 38791caf5bfc..c9160efb75a4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt @@ -17,14 +17,20 @@ package com.android.systemui.keyguard.data.repository +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull -class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository { +@SysUISingleton +class FakeDeviceEntryFingerprintAuthRepository @Inject constructor() : + DeviceEntryFingerprintAuthRepository { private val _isLockedOut = MutableStateFlow(false) override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow() fun setLockedOut(lockedOut: Boolean) { @@ -52,3 +58,11 @@ class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepos _authenticationStatus.value = status } } + +@Module +interface FakeDeviceEntryFingerprintAuthRepositoryModule { + @Binds + fun bindFake( + fake: FakeDeviceEntryFingerprintAuthRepository + ): DeviceEntryFingerprintAuthRepository +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index b90ad8cd8745..3674244926e8 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 @@ -151,6 +151,17 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio _transitions.emit(step) } + suspend fun sendTransitionSteps( + steps: List<TransitionStep>, + testScope: TestScope, + validateStep: Boolean = true + ) { + steps.forEach { + sendTransitionStep(it, validateStep = validateStep) + testScope.testScheduler.runCurrent() + } + } + override fun startTransition(info: TransitionInfo): UUID? { return if (info.animator == null) UUID.randomUUID() else null } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt index 1efa74b0551a..62765d10486c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt @@ -20,7 +20,6 @@ import android.os.UserHandle class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor { - var handleResult: Boolean = false var policyResult: DisabledByPolicyInteractor.PolicyResult = DisabledByPolicyInteractor.PolicyResult.TileEnabled @@ -31,5 +30,9 @@ class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor { override fun handlePolicyResult( policyResult: DisabledByPolicyInteractor.PolicyResult - ): Boolean = handleResult + ): Boolean = + when (policyResult) { + is DisabledByPolicyInteractor.PolicyResult.TileEnabled -> false + is DisabledByPolicyInteractor.PolicyResult.TileDisabled -> true + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt index 2b3330f3a33b..3fcf8a93dc87 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt @@ -17,16 +17,21 @@ package com.android.systemui.qs.tiles.base.interactor import android.os.UserHandle -import javax.annotation.CheckReturnValue import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.flatMapLatest -class FakeQSTileDataInteractor<T>( - private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = Int.MAX_VALUE), - private val availabilityFlow: MutableSharedFlow<Boolean> = - MutableSharedFlow(replay = Int.MAX_VALUE), -) : QSTileDataInteractor<T> { +class FakeQSTileDataInteractor<T> : QSTileDataInteractor<T> { + + private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = 1) + val dataSubscriptionCount + get() = dataFlow.subscriptionCount + private val availabilityFlow: MutableSharedFlow<Boolean> = MutableSharedFlow(replay = 1) + val availabilitySubscriptionCount + get() = availabilityFlow.subscriptionCount + + private val mutableTriggers = mutableListOf<DataUpdateTrigger>() + val triggers: List<DataUpdateTrigger> = mutableTriggers private val mutableDataRequests = mutableListOf<DataRequest>() val dataRequests: List<DataRequest> = mutableDataRequests @@ -34,14 +39,17 @@ class FakeQSTileDataInteractor<T>( private val mutableAvailabilityRequests = mutableListOf<AvailabilityRequest>() val availabilityRequests: List<AvailabilityRequest> = mutableAvailabilityRequests - @CheckReturnValue fun emitData(data: T): Boolean = dataFlow.tryEmit(data) + suspend fun emitData(data: T): Unit = dataFlow.emit(data) fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable) suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable) override fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<T> { mutableDataRequests.add(DataRequest(user)) - return triggers.flatMapLatest { dataFlow } + return triggers.flatMapLatest { + mutableTriggers.add(it) + dataFlow + } } override fun availability(user: UserHandle): Flow<Boolean> { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/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/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java index 4c224fb33b26..fb521e11c083 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java @@ -359,7 +359,7 @@ public class WallpaperBackupAgentTest { } @Test - public void testUpdateWallpaperComponent_doesApplyLater() throws IOException { + public void testUpdateWallpaperComponent_systemAndLock() throws IOException { mWallpaperBackupAgent.mIsDeviceInRestore = true; mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, /* which */ FLAG_LOCK | FLAG_SYSTEM); @@ -377,7 +377,7 @@ public class WallpaperBackupAgentTest { } @Test - public void testUpdateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen() + public void testUpdateWallpaperComponent_systemOnly() throws IOException { mWallpaperBackupAgent.mIsDeviceInRestore = true; @@ -617,7 +617,7 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.onRestoreFinished(); - // wallpaper will be applied to home & lock screen, a success for both screens in expected + // wallpaper will be applied to home & lock screen, a success for both screens is expected DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); assertThat(result).isNotNull(); diff --git a/ravenwood/README-ravenwood+mockito.md b/ravenwood/README-ravenwood+mockito.md new file mode 100644 index 000000000000..6adb61441bb1 --- /dev/null +++ b/ravenwood/README-ravenwood+mockito.md @@ -0,0 +1,24 @@ +# Ravenwood and Mockito + +Last update: 2023-11-13 + +- As of 2023-11-13, `external/mockito` is based on version 2.x. +- Mockito didn't support static mocking before 3.4.0. + See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48 + +- Latest Mockito is 5.*. According to https://github.com/mockito/mockito: + `Mockito 3 does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. Mockito 4 removes deprecated API. Mockito 5 switches the default mockmaker to mockito-inline, and now requires Java 11.` + +- Mockito now supports Android natively. + See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.1 + - But it's unclear at this point to omakoto@ how the `mockito-android` module is built. + +- Potential plan: + - Ideal option: + - If we can update `external/mockito`, that'd be great, but it may not work because + Mockito has removed the deprecated APIs. + - Second option: + - Import the latest mockito as `external/mockito-new`, and require ravenwood + to use this one. + - The latest mockito needs be exposed to all of 1) device tests, 2) host tests, and 3) ravenwood tests. + - This probably will require the latest `bytebuddy` and `objenesis`.
\ No newline at end of file diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp index 6dbff4c962ba..4135022d2bc9 100644 --- a/ravenwood/mockito/Android.bp +++ b/ravenwood/mockito/Android.bp @@ -36,3 +36,37 @@ android_ravenwood_test { ], auto_gen_config: true, } + +android_test { + name: "RavenwoodMockitoTest_device", + + srcs: [ + "test/**/*.java", + ], + static_libs: [ + "junit", + "truth", + + "androidx.test.rules", + + "ravenwood-junit", + + "mockito-target-extended-minus-junit4", + ], + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + jni_libs: [ + // Required by mockito + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + test_suites: [ + "device-tests", + ], + optimize: { + enabled: false, + }, +} diff --git a/ravenwood/mockito/AndroidManifest.xml b/ravenwood/mockito/AndroidManifest.xml new file mode 100644 index 000000000000..15f0a2934b5f --- /dev/null +++ b/ravenwood/mockito/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.ravenwood.mockitotest"> + + <application android:debuggable="true" > + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.ravenwood.mockitotest" + /> +</manifest> diff --git a/ravenwood/mockito/AndroidTest.xml b/ravenwood/mockito/AndroidTest.xml new file mode 100644 index 000000000000..96bc2752fe95 --- /dev/null +++ b/ravenwood/mockito/AndroidTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Frameworks Services Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="RavenwoodMockitoTest_device.apk" /> + </target_preparer> + + <option name="test-tag" value="FrameworksMockingServicesTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.ravenwood.mockitotest" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/MockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/MockitoTest.java deleted file mode 100644 index b175ae74891b..000000000000 --- a/ravenwood/mockito/test/com/android/ravenwood/mockito/MockitoTest.java +++ /dev/null @@ -1,67 +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.ravenwood.mockito; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.Intent; - -import org.junit.Test; - -public class MockitoTest { - @Test - public void testMockJdkClass() { - Process object = mock(Process.class); - - when(object.exitValue()).thenReturn(42); - - assertThat(object.exitValue()).isEqualTo(42); - } - - /* It still doesn't work... -STACKTRACE: -org.mockito.exceptions.base.MockitoException: -Mockito cannot mock this class: class android.content.Intent. - -Mockito can only mock non-private & non-final classes. -If you're not sure why you're getting this error, please report to the mailing list. - - -... But Intent public, non-final. - - */ - // @Test - private void testMockAndroidClass1() { - Intent object = mock(Intent.class); - - when(object.getAction()).thenReturn("ACTION_RAVENWOOD"); - - assertThat(object.getAction()).isEqualTo("ACTION_RAVENWOOD"); - } - - @Test - public void testMockAndroidClass2() { - Context object = mock(Context.class); - - when(object.getPackageName()).thenReturn("android"); - - assertThat(object.getPackageName()).isEqualTo("android"); - } -} diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java new file mode 100644 index 000000000000..36fa3dd94e29 --- /dev/null +++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ravenwood.mockito; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import org.junit.Rule; +import org.junit.Test; + +public class RavenwoodMockitoTest { + @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + +// Use this to mock static methods, which isn't supported by mockito 2. +// Mockito supports static mocking since 3.4.0: +// See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48 + +// private MockitoSession mMockingSession; +// +// @Before +// public void setUp() { +// mMockingSession = mockitoSession() +// .strictness(Strictness.LENIENT) +// .mockStatic(RavenwoodMockitoTest.class) +// .startMocking(); +// } +// +// @After +// public void tearDown() { +// if (mMockingSession != null) { +// mMockingSession.finishMocking(); +// } +// } + + @Test + public void testMockJdkClass() { + Process object = mock(Process.class); + + when(object.exitValue()).thenReturn(42); + + assertThat(object.exitValue()).isEqualTo(42); + } + + /* + - Intent can't be mocked because of the dependency to `org.xmlpull.v1.XmlPullParser`. + (The error says "Mockito can only mock non-private & non-final classes", but that's likely a + red-herring.) + +STACKTRACE: +org.mockito.exceptions.base.MockitoException: +Mockito cannot mock this class: class android.content.Intent. + + : + +Underlying exception : java.lang.IllegalArgumentException: Could not create type + at com.android.ravenwood.mockito.RavenwoodMockitoTest.testMockAndroidClass1 + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + : + +Caused by: java.lang.ClassNotFoundException: org.xmlpull.v1.XmlPullParser + at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) + at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) + at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) + ... 54 more + */ + @Test + @IgnoreUnderRavenwood + public void testMockAndroidClass1() { + Intent object = mock(Intent.class); + + when(object.getAction()).thenReturn("ACTION_RAVENWOOD"); + + assertThat(object.getAction()).isEqualTo("ACTION_RAVENWOOD"); + } + + @Test + public void testMockAndroidClass2() { + Context object = mock(Context.class); + + when(object.getPackageName()).thenReturn("android"); + + assertThat(object.getPackageName()).isEqualTo("android"); + } +} diff --git a/services/companion/Android.bp b/services/companion/Android.bp index fb8db21c3090..550e17be276d 100644 --- a/services/companion/Android.bp +++ b/services/companion/Android.bp @@ -21,7 +21,6 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [ ":services.companion-sources", - ":VirtualCamera-aidl-sources", ], libs: [ "app-compat-annotations", @@ -30,13 +29,6 @@ java_library_static { static_libs: [ "ukey2_jni", "virtualdevice_flags_lib", + "virtual_camera_service_aidl-java", ], } - -filegroup { - name: "VirtualCamera-aidl-sources", - srcs: [ - "java/com/android/server/companion/virtual/camera/*.aidl", - ], - path: "java", -} diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 2c608930b391..b9c269c91651 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -522,7 +522,8 @@ public class CompanionDeviceManagerService extends SystemService { private void notifyListeners( @UserIdInt int userId, @NonNull List<AssociationInfo> associations) { mListeners.broadcast((listener, callbackUserId) -> { - if ((int) callbackUserId == userId) { + int listenerUserId = (int) callbackUserId; + if (listenerUserId == userId || listenerUserId == UserHandle.USER_ALL) { try { listener.onAssociationsChanged(associations); } catch (RemoteException ignored) { @@ -660,6 +661,9 @@ public class CompanionDeviceManagerService extends SystemService { enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId); + if (userId == UserHandle.USER_ALL) { + return List.copyOf(mAssociationStore.getAssociations()); + } return mAssociationStore.getAssociationsForUser(userId); } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index ae8fddfd35ef..118943df1bf6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,7 +51,7 @@ import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; -import android.companion.virtual.camera.IVirtualCamera; +import android.companion.virtual.camera.VirtualCameraConfig; import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorEvent; @@ -277,7 +277,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub runningAppsChangedCallback, params, DisplayManagerGlobal.getInstance(), - Flags.virtualCamera() ? new VirtualCameraController(context) : null); + Flags.virtualCamera() ? new VirtualCameraController() : null); } @VisibleForTesting @@ -304,7 +304,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid()); mContext = context.createContextAsUser(ownerUserHandle, 0); mAssociationInfo = associationInfo; - mPersistentDeviceId = PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationInfo.getId(); + mPersistentDeviceId = createPersistentDeviceId(associationInfo.getId()); mService = service; mPendingTrampolineCallback = pendingTrampolineCallback; mActivityListener = activityListener; @@ -380,6 +380,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mSensorController; } + static String createPersistentDeviceId(int associationId) { + return PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationId; + } + /** * Returns the flags that should be added to any virtual displays created on this virtual * device. @@ -688,7 +692,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub final long ident = Binder.clearCallingIdentity(); try { mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(), - config.getProductId(), deviceToken, config.getAssociatedDisplayId()); + config.getProductId(), deviceToken, + getTargetDisplayIdForInput(config.getAssociatedDisplayId())); } finally { Binder.restoreCallingIdentity(ident); } @@ -706,7 +711,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub final long ident = Binder.clearCallingIdentity(); try { mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(), - config.getProductId(), deviceToken, config.getAssociatedDisplayId(), + config.getProductId(), deviceToken, + getTargetDisplayIdForInput(config.getAssociatedDisplayId()), config.getLanguageTag(), config.getLayoutType()); } finally { Binder.restoreCallingIdentity(ident); @@ -772,7 +778,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub try { mInputController.createNavigationTouchpad( config.getInputDeviceName(), config.getVendorId(), - config.getProductId(), deviceToken, config.getAssociatedDisplayId(), + config.getProductId(), deviceToken, + getTargetDisplayIdForInput(config.getAssociatedDisplayId()), touchpadHeight, touchpadWidth); } finally { Binder.restoreCallingIdentity(ident); @@ -950,13 +957,28 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + @Override // Binder call @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) - public void registerVirtualCamera(@NonNull IVirtualCamera camera) { + public void registerVirtualCamera(@NonNull VirtualCameraConfig cameraConfig) + throws RemoteException { super.registerVirtualCamera_enforcePermission(); + Objects.requireNonNull(cameraConfig); if (mVirtualCameraController == null) { - return; + throw new UnsupportedOperationException("Virtual camera controller is not available"); } - mVirtualCameraController.registerCamera(Objects.requireNonNull(camera)); + mVirtualCameraController.registerCamera(Objects.requireNonNull(cameraConfig)); + } + + @Override // Binder call + @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void unregisterVirtualCamera(@NonNull VirtualCameraConfig cameraConfig) + throws RemoteException { + super.unregisterVirtualCamera_enforcePermission(); + Objects.requireNonNull(cameraConfig); + if (mVirtualCameraController == null) { + throw new UnsupportedOperationException("Virtual camera controller is not available"); + } + mVirtualCameraController.unregisterCamera(Objects.requireNonNull(cameraConfig)); } @Override @@ -983,6 +1005,20 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + // For display mirroring, we want to dispatch all key events to the source (default) display, + // as the virtual display doesn't have any focused windows. Hence, call this for + // associating any input device to the source display if the input device emits any key events. + private int getTargetDisplayIdForInput(int displayId) { + if (!Flags.interactiveScreenMirror()) { + return displayId; + } + + DisplayManagerInternal displayManager = LocalServices.getService( + DisplayManagerInternal.class); + int mirroredDisplayId = displayManager.getDisplayIdToMirror(displayId); + return mirroredDisplayId == Display.INVALID_DISPLAY ? displayId : mirroredDisplayId; + } + @GuardedBy("mVirtualDeviceLock") private GenericWindowPolicyController createWindowPolicyControllerLocked( @NonNull Set<String> displayCategories) { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 85b3c9a2b33c..215970eedff1 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -27,6 +27,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.app.ActivityOptions; import android.companion.AssociationInfo; +import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; @@ -94,6 +95,11 @@ public class VirtualDeviceManagerService extends SystemService { private static final String VIRTUAL_DEVICE_NATIVE_SERVICE = "virtualdevice_native"; + private static final List<String> VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES = Arrays.asList( + AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, + AssociationRequest.DEVICE_PROFILE_APP_STREAMING, + AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING); + private final Object mVirtualDeviceManagerLock = new Object(); private final VirtualDeviceManagerImpl mImpl; private final VirtualDeviceManagerNativeImpl mNativeImpl; @@ -105,6 +111,9 @@ public class VirtualDeviceManagerService extends SystemService { private static AtomicInteger sNextUniqueIndex = new AtomicInteger( Context.DEVICE_ID_DEFAULT + 1); + @GuardedBy("mVirtualDeviceManagerLock") + private List<AssociationInfo> mActiveAssociations = new ArrayList<>(); + private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener = new CompanionDeviceManager.OnAssociationsChangedListener() { @Override @@ -161,6 +170,7 @@ public class VirtualDeviceManagerService extends SystemService { }; @Override + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public void onStart() { publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl); if (Flags.enableNativeVdm()) { @@ -172,6 +182,21 @@ public class VirtualDeviceManagerService extends SystemService { activityTaskManagerInternal.registerActivityStartInterceptor( VIRTUAL_DEVICE_SERVICE_ORDERED_ID, mActivityInterceptorCallback); + + if (Flags.persistentDeviceIdApi()) { + CompanionDeviceManager cdm = + getContext().getSystemService(CompanionDeviceManager.class); + if (cdm != null) { + synchronized (mVirtualDeviceManagerLock) { + mActiveAssociations = cdm.getAllAssociations(UserHandle.USER_ALL); + } + cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(), + this::onCdmAssociationsChanged, UserHandle.USER_ALL); + } else { + Slog.e(TAG, "Failed to find CompanionDeviceManager. No CDM association info " + + " will be available."); + } + } } void onCameraAccessBlocked(int appUid) { @@ -264,9 +289,11 @@ public class VirtualDeviceManagerService extends SystemService { try { getContext().sendBroadcastAsUser(i, UserHandle.ALL); - synchronized (mVirtualDeviceManagerLock) { - if (mVirtualDevices.size() == 0) { - unregisterCdmAssociationListener(); + if (!Flags.persistentDeviceIdApi()) { + synchronized (mVirtualDeviceManagerLock) { + if (mVirtualDevices.size() == 0) { + unregisterCdmAssociationListener(); + } } } } finally { @@ -316,6 +343,45 @@ public class VirtualDeviceManagerService extends SystemService { cdm.removeOnAssociationsChangedListener(mCdmAssociationListener); } + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + void onCdmAssociationsChanged(List<AssociationInfo> associations) { + Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>(); + Set<String> removedPersistentDeviceIds = new HashSet<>(); + synchronized (mVirtualDeviceManagerLock) { + Set<Integer> activeAssociationIds = new HashSet<>(associations.size()); + for (int i = 0; i < associations.size(); ++i) { + activeAssociationIds.add(associations.get(i).getId()); + } + + for (int i = 0; i < mActiveAssociations.size(); ++i) { + AssociationInfo associationInfo = mActiveAssociations.get(i); + if (!activeAssociationIds.contains(associationInfo.getId()) + && VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains( + associationInfo.getDeviceProfile())) { + removedPersistentDeviceIds.add( + VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId())); + } + } + + for (int i = 0; i < mVirtualDevices.size(); i++) { + VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i); + if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) { + virtualDevicesToRemove.add(virtualDevice); + } + } + + mActiveAssociations = associations; + } + + for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) { + virtualDevice.close(); + } + + if (!removedPersistentDeviceIds.isEmpty()) { + mLocalService.onPersistentDeviceIdsRemoved(removedPersistentDeviceIds); + } + } + private ArrayList<VirtualDeviceImpl> getVirtualDevicesSnapshot() { synchronized (mVirtualDeviceManagerLock) { ArrayList<VirtualDeviceImpl> virtualDevices = new ArrayList<>(mVirtualDevices.size()); @@ -393,7 +459,7 @@ public class VirtualDeviceManagerService extends SystemService { } synchronized (mVirtualDeviceManagerLock) { - if (mVirtualDevices.size() == 0) { + if (!Flags.persistentDeviceIdApi() && mVirtualDevices.size() == 0) { final long callindId = Binder.clearCallingIdentity(); try { registerCdmAssociationListener(); @@ -623,8 +689,12 @@ public class VirtualDeviceManagerService extends SystemService { private final class LocalService extends VirtualDeviceManagerInternal { @GuardedBy("mVirtualDeviceManagerLock") - private final ArrayList<AppsOnVirtualDeviceListener> - mAppsOnVirtualDeviceListeners = new ArrayList<>(); + private final ArrayList<AppsOnVirtualDeviceListener> mAppsOnVirtualDeviceListeners = + new ArrayList<>(); + @GuardedBy("mVirtualDeviceManagerLock") + private final ArrayList<Consumer<String>> mPersistentDeviceIdRemovedListeners = + new ArrayList<>(); + @GuardedBy("mVirtualDeviceManagerLock") private final ArraySet<Integer> mAllUidsOnVirtualDevice = new ArraySet<>(); @@ -700,6 +770,22 @@ public class VirtualDeviceManagerService extends SystemService { } @Override + public void onPersistentDeviceIdsRemoved(Set<String> removedPersistentDeviceIds) { + final List<Consumer<String>> persistentDeviceIdRemovedListeners; + synchronized (mVirtualDeviceManagerLock) { + persistentDeviceIdRemovedListeners = List.copyOf( + mPersistentDeviceIdRemovedListeners); + } + mHandler.post(() -> { + for (String persistentDeviceId : removedPersistentDeviceIds) { + for (Consumer<String> listener : persistentDeviceIdRemovedListeners) { + listener.accept(persistentDeviceId); + } + } + }); + } + + @Override public void onAuthenticationPrompt(int uid) { synchronized (mVirtualDeviceManagerLock) { for (int i = 0; i < mVirtualDevices.size(); i++) { @@ -766,6 +852,10 @@ public class VirtualDeviceManagerService extends SystemService { @Override public @Nullable String getPersistentIdForDevice(int deviceId) { + if (deviceId == Context.DEVICE_ID_DEFAULT) { + return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; + } + VirtualDeviceImpl virtualDevice; synchronized (mVirtualDeviceManagerLock) { virtualDevice = mVirtualDevices.get(deviceId); @@ -788,6 +878,22 @@ public class VirtualDeviceManagerService extends SystemService { mAppsOnVirtualDeviceListeners.remove(listener); } } + + @Override + public void registerPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener) { + synchronized (mVirtualDeviceManagerLock) { + mPersistentDeviceIdRemovedListeners.add(persistentDeviceIdRemovedListener); + } + } + + @Override + public void unregisterPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener) { + synchronized (mVirtualDeviceManagerLock) { + mPersistentDeviceIdRemovedListeners.remove(persistentDeviceIdRemovedListener); + } + } } private static final class PendingTrampolineMap { diff --git a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl b/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl deleted file mode 100644 index a4c1c4249697..000000000000 --- a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.virtual.camera; - -import android.companion.virtual.camera.IVirtualCamera; -import android.companion.virtual.camera.VirtualCameraHalConfig; - -/** - * AIDL Interface to communicate with the VirtualCamera HAL - * @hide - */ -interface IVirtualCameraService { - - /** - * Registers a new camera with the virtual camera hal. - * @return true if the camera was successfully registered - */ - boolean registerCamera(in IVirtualCamera camera); - - /** - * Unregisters the camera from the virtual camera hal. After this call the virtual camera won't - * be visible to the camera framework anymore. - */ - void unregisterCamera(in IVirtualCamera camera); -} diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java index 031d94962844..06be3f39dcd1 100644 --- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java +++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java @@ -16,207 +16,127 @@ package com.android.server.companion.virtual.camera; +import static com.android.server.companion.virtual.camera.VirtualCameraConversionUtil.getServiceCameraConfiguration; + import android.annotation.NonNull; import android.annotation.Nullable; -import android.companion.virtual.camera.IVirtualCamera; -import android.companion.virtual.camera.VirtualCameraHalConfig; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; +import android.companion.virtual.camera.VirtualCameraConfig; +import android.companion.virtualcamera.IVirtualCameraService; +import android.companion.virtualcamera.VirtualCameraConfiguration; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.os.UserHandle; -import android.util.Log; +import android.os.ServiceManager; +import android.util.ArraySet; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; -import java.util.HashMap; -import java.util.Map; +import java.util.Set; /** * Manages the registration and removal of virtual camera from the server side. * * <p>This classes delegate calls to the virtual camera service, so it is dependent on the service - * to be up and running + * to be up and running. */ -public class VirtualCameraController implements IBinder.DeathRecipient, ServiceConnection { - - private static class VirtualCameraInfo { - - private final IVirtualCamera mVirtualCamera; - private boolean mIsRegistered; - - VirtualCameraInfo(IVirtualCamera virtualCamera) { - mVirtualCamera = virtualCamera; - } - } +public final class VirtualCameraController implements IBinder.DeathRecipient { + private static final String VIRTUAL_CAMERA_SERVICE_NAME = "virtual_camera"; private static final String TAG = "VirtualCameraController"; - private static final String VIRTUAL_CAMERA_SERVICE_PACKAGE = "com.android.virtualcamera"; - private static final String VIRTUAL_CAMERA_SERVICE_CLASS = ".VirtualCameraService"; - private final Context mContext; - - @Nullable private IVirtualCameraService mVirtualCameraService = null; + @Nullable private IVirtualCameraService mVirtualCameraService; @GuardedBy("mCameras") - private final Map<IVirtualCamera, VirtualCameraInfo> mCameras = new HashMap<>(1); + private final Set<VirtualCameraConfig> mCameras = new ArraySet<>(); - public VirtualCameraController(Context context) { - mContext = context; + public VirtualCameraController() { connectVirtualCameraService(); } - private void connectVirtualCameraService() { - final long callingId = Binder.clearCallingIdentity(); - try { - Intent intent = new Intent(); - intent.setPackage(VIRTUAL_CAMERA_SERVICE_PACKAGE); - intent.setComponent( - ComponentName.createRelative( - VIRTUAL_CAMERA_SERVICE_PACKAGE, VIRTUAL_CAMERA_SERVICE_CLASS)); - mContext.startServiceAsUser(intent, UserHandle.SYSTEM); - if (!mContext.bindServiceAsUser( - intent, - this, - Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, - UserHandle.SYSTEM)) { - mContext.unbindService(this); - Log.w( - TAG, - "connectVirtualCameraService: Failed to connect to the virtual camera " - + "service"); - } - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - - private void forwardPendingRegistrations() { - IVirtualCameraService cameraService = mVirtualCameraService; - if (cameraService == null) { - return; - } - synchronized (mCameras) { - for (VirtualCameraInfo cameraInfo : mCameras.values()) { - if (cameraInfo.mIsRegistered) { - continue; - } - try { - cameraService.registerCamera(cameraInfo.mVirtualCamera); - cameraInfo.mIsRegistered = true; - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - } + @VisibleForTesting + VirtualCameraController(IVirtualCameraService virtualCameraService) { + mVirtualCameraService = virtualCameraService; } /** - * Remove the virtual camera with the provided name + * Register a new virtual camera with the given config. * - * @param camera The name of the camera to remove + * @param cameraConfig The {@link VirtualCameraConfig} sent by the client. */ - public void unregisterCamera(@NonNull IVirtualCamera camera) { - IVirtualCameraService virtualCameraService = mVirtualCameraService; - if (virtualCameraService != null) { - try { - virtualCameraService.unregisterCamera(camera); + public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) { + // Try to connect to service if not connected already. + if (mVirtualCameraService == null) { + connectVirtualCameraService(); + } + // Throw exception if we are unable to connect to service. + if (mVirtualCameraService == null) { + throw new IllegalStateException("Virtual camera service is not connected."); + } + + try { + if (registerCameraWithService(cameraConfig)) { synchronized (mCameras) { - VirtualCameraInfo cameraInfo = mCameras.remove(camera); - cameraInfo.mIsRegistered = false; + mCameras.add(cameraConfig); } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + } else { + // TODO(b/310857519): Revisit this to find a better way of indicating failure. + throw new RuntimeException("Failed to register virtual camera."); } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } } /** - * Register a new virtual camera with the provided characteristics. + * Unregister the virtual camera with the given config. * - * @param camera The {@link IVirtualCamera} producing the image to communicate with the client. - * @throws IllegalArgumentException if the characteristics could not be parsed. + * @param cameraConfig The {@link VirtualCameraConfig} sent by the client. */ - public void registerCamera(@NonNull IVirtualCamera camera) { - IVirtualCameraService service = mVirtualCameraService; - VirtualCameraInfo virtualCameraInfo = new VirtualCameraInfo(camera); - synchronized (mCameras) { - mCameras.put(camera, virtualCameraInfo); - } - if (service != null) { - try { - if (service.registerCamera(camera)) { - virtualCameraInfo.mIsRegistered = true; - return; - } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + public void unregisterCamera(@NonNull VirtualCameraConfig cameraConfig) { + try { + if (mVirtualCameraService == null) { + Slog.w(TAG, "Virtual camera service is not connected."); + } else { + mVirtualCameraService.unregisterCamera(cameraConfig.getCallback().asBinder()); } + synchronized (mCameras) { + mCameras.remove(cameraConfig); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } - - // Service was not available or registration failed, save the registration for later - connectVirtualCameraService(); } @Override public void binderDied() { - Log.d(TAG, "binderDied"); + Slog.d(TAG, "Virtual camera service died."); mVirtualCameraService = null; - } - - @Override - public void onBindingDied(ComponentName name) { - mVirtualCameraService = null; - Log.d(TAG, "onBindingDied() called with: name = [" + name + "]"); - } - - @Override - public void onNullBinding(ComponentName name) { - mVirtualCameraService = null; - Log.d(TAG, "onNullBinding() called with: name = [" + name + "]"); - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - Log.d(TAG, "onServiceConnected: " + name.toString()); - mVirtualCameraService = IVirtualCameraService.Stub.asInterface(service); - try { - service.linkToDeath(this, 0); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); + synchronized (mCameras) { + mCameras.clear(); } - forwardPendingRegistrations(); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - Log.d(TAG, "onServiceDisconnected() called with: name = [" + name + "]"); - mVirtualCameraService = null; } /** Release resources associated with this controller. */ public void close() { - if (mVirtualCameraService == null) { - return; - } synchronized (mCameras) { - mCameras.forEach( - (name, cameraInfo) -> { - try { - mVirtualCameraService.unregisterCamera(name); - } catch (RemoteException e) { - Log.w( - TAG, - "close(): Camera failed to be removed on camera service.", - e); - } - }); + if (mVirtualCameraService == null) { + Slog.w(TAG, "Virtual camera service is not connected."); + } else { + for (VirtualCameraConfig config : mCameras) { + try { + mVirtualCameraService.unregisterCamera(config.getCallback().asBinder()); + } catch (RemoteException e) { + Slog.w(TAG, "close(): Camera failed to be removed on camera " + + "service.", e); + } + } + } + mCameras.clear(); } - mContext.unbindService(this); + mVirtualCameraService = null; } /** Dumps information about this {@link VirtualCameraController} for debugging purposes. */ @@ -226,20 +146,34 @@ public class VirtualCameraController implements IBinder.DeathRecipient, ServiceC fout.printf("%sService:%s\n", indent, mVirtualCameraService); synchronized (mCameras) { fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size()); - for (VirtualCameraInfo info : mCameras.values()) { - VirtualCameraHalConfig config = null; - try { - config = info.mVirtualCamera.getHalConfig(); - } catch (RemoteException ex) { - Log.w(TAG, ex); - } - fout.printf( - "%s- %s isRegistered: %s, token: %s\n", - indent, - config == null ? "" : config.displayName, - info.mIsRegistered, - info.mVirtualCamera); + for (VirtualCameraConfig config : mCameras) { + fout.printf("%s token: %s\n", indent, config); + } + } + } + + private void connectVirtualCameraService() { + final long callingId = Binder.clearCallingIdentity(); + try { + IBinder virtualCameraBinder = + ServiceManager.waitForService(VIRTUAL_CAMERA_SERVICE_NAME); + if (virtualCameraBinder == null) { + Slog.e(TAG, "connectVirtualCameraService: Failed to connect to the virtual " + + "camera service"); + return; } + virtualCameraBinder.linkToDeath(this, 0); + mVirtualCameraService = IVirtualCameraService.Stub.asInterface(virtualCameraBinder); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } finally { + Binder.restoreCallingIdentity(callingId); } } + + private boolean registerCameraWithService(VirtualCameraConfig config) throws RemoteException { + VirtualCameraConfiguration serviceConfiguration = getServiceCameraConfiguration(config); + return mVirtualCameraService.registerCamera(config.getCallback().asBinder(), + serviceConfiguration); + } } diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java new file mode 100644 index 000000000000..202f68bdeb4a --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual.camera; + +import android.annotation.NonNull; +import android.companion.virtual.camera.IVirtualCameraCallback; +import android.companion.virtual.camera.VirtualCameraConfig; +import android.companion.virtual.camera.VirtualCameraStreamConfig; +import android.companion.virtualcamera.IVirtualCameraService; +import android.companion.virtualcamera.SupportedStreamConfiguration; +import android.companion.virtualcamera.VirtualCameraConfiguration; +import android.os.RemoteException; +import android.view.Surface; + +/** Utilities to convert the client side classes to the virtual camera service ones. */ +public final class VirtualCameraConversionUtil { + + /** + * Fetches the configuration of the provided virtual cameraConfig that was provided by its owner + * and convert it into the {@link IVirtualCameraService} types: {@link + * VirtualCameraConfiguration}. + * + * @param cameraConfig The cameraConfig sent by the client. + * @return The converted configuration to be sent to the {@link IVirtualCameraService}. + * @throws RemoteException If there was an issue fetching the configuration from the client. + */ + @NonNull + public static android.companion.virtualcamera.VirtualCameraConfiguration + getServiceCameraConfiguration(@NonNull VirtualCameraConfig cameraConfig) + throws RemoteException { + VirtualCameraConfiguration serviceConfiguration = new VirtualCameraConfiguration(); + + serviceConfiguration.supportedStreamConfigs = + cameraConfig.getStreamConfigs().stream() + .map(VirtualCameraConversionUtil::convertSupportedStreamConfiguration) + .toArray(SupportedStreamConfiguration[]::new); + + serviceConfiguration.virtualCameraCallback = convertCallback(cameraConfig.getCallback()); + return serviceConfiguration; + } + + @NonNull + private static android.companion.virtualcamera.IVirtualCameraCallback convertCallback( + @NonNull IVirtualCameraCallback camera) { + return new android.companion.virtualcamera.IVirtualCameraCallback.Stub() { + @Override + public void onStreamConfigured( + int streamId, Surface surface, int width, int height, int pixelFormat) + throws RemoteException { + VirtualCameraStreamConfig streamConfig = + createStreamConfig(width, height, pixelFormat); + camera.onStreamConfigured(streamId, surface, streamConfig); + } + + @Override + public void onStreamClosed(int streamId) throws RemoteException { + camera.onStreamClosed(streamId); + } + }; + } + + @NonNull + private static VirtualCameraStreamConfig createStreamConfig( + int width, int height, int pixelFormat) { + return new VirtualCameraStreamConfig(width, height, pixelFormat); + } + + @NonNull + private static SupportedStreamConfiguration convertSupportedStreamConfiguration( + VirtualCameraStreamConfig stream) { + SupportedStreamConfiguration supportedConfig = new SupportedStreamConfiguration(); + supportedConfig.height = stream.getHeight(); + supportedConfig.width = stream.getWidth(); + supportedConfig.pixelFormat = stream.getFormat(); + return supportedConfig; + } + + private VirtualCameraConversionUtil() { + } +} diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 065a447a7cd1..4b004340f923 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -43,6 +43,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.util.function.pooled.PooledLambda; import com.android.permission.persistence.RuntimePermissionsState; import com.android.server.pm.Installer.LegacyDexoptDisabledException; @@ -54,7 +55,6 @@ import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import com.android.server.pm.snapshot.PackageDataSnapshot; 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/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 9bba08ad1bfd..ddccce5963b3 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2230,8 +2230,10 @@ public class OomAdjuster { || now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) { // This service has seen some activity within // recent memory, so we will keep its process ahead - // of the background processes. - if (adj > SERVICE_ADJ) { + // of the background processes. This does not apply + // to the SDK sandbox process since it should never + // be more important than its corresponding app. + if (!app.isSdkSandbox && adj > SERVICE_ADJ) { adj = SERVICE_ADJ; state.setAdjType("started-services"); if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index e5f763722bf1..7292ea6b19cb 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -148,6 +148,7 @@ import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.Clock; +import com.android.internal.pm.pkg.component.ParsedAttribution; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -165,7 +166,6 @@ import com.android.server.pm.PackageManagerLocal; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; -import com.android.server.pm.pkg.component.ParsedAttribution; import com.android.server.policy.AppOpsPolicy; import dalvik.annotation.optimization.NeverCompile; diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 0d7f778bb326..c629b2b91603 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -25,6 +25,7 @@ import android.os.LocaleList; import android.util.ArraySet; import java.util.Set; +import java.util.function.Consumer; /** * Virtual device manager local service interface. @@ -46,6 +47,14 @@ public abstract class VirtualDeviceManagerInternal { public abstract void unregisterAppsOnVirtualDeviceListener( @NonNull AppsOnVirtualDeviceListener listener); + /** Register a listener for removal of persistent device IDs. */ + public abstract void registerPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener); + + /** Unregister a listener for the removal of persistent device IDs. */ + public abstract void unregisterPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener); + /** * Notifies that the set of apps running on virtual devices has changed. * This method only notifies the listeners when the union of running UIDs on all virtual devices @@ -59,6 +68,11 @@ public abstract class VirtualDeviceManagerInternal { public abstract void onAuthenticationPrompt(int uid); /** + * Notifies the given persistent device IDs have been removed. + */ + public abstract void onPersistentDeviceIdsRemoved(Set<String> removedPersistentDeviceIds); + + /** * Gets the owner uid for a deviceId. * * @param deviceId which device we're asking about 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/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index d3eedd7e089a..168715713f8d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -191,9 +191,16 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { launchDeviceDiscovery(); startQueuedActions(); if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { - mService.sendCecCommand( - HdmiCecMessageBuilder.buildRequestActiveSource( - getDeviceInfo().getLogicalAddress())); + addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + if (result != HdmiControlManager.RESULT_SUCCESS) { + mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource( + getDeviceInfo().getLogicalAddress(), + getDeviceInfo().getPhysicalAddress())); + } + } + })); } } @@ -1325,6 +1332,8 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { removeAction(TimerRecordingAction.class); removeAction(NewDeviceAction.class); removeAction(AbsoluteVolumeAudioStatusAction.class); + // Remove pending actions. + removeAction(RequestActiveSourceAction.class); // Keep SAM enabled if eARC is enabled, unless we're going to Standby. if (initiatedByCec || !mService.isEarcEnabled()){ diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java new file mode 100644 index 000000000000..017c86d4b363 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; + +/** + * Feature action that sends <Request Active Source> message and waits for <Active Source>. + */ +public class RequestActiveSourceAction extends HdmiCecFeatureAction { + private static final String TAG = "RequestActiveSourceAction"; + + // State to wait for the <Active Source> message. + private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 1; + + RequestActiveSourceAction(HdmiCecLocalDevice source, IHdmiControlCallback callback) { + super(source, callback); + } + + @Override + boolean start() { + Slog.v(TAG, "RequestActiveSourceAction started."); + + sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress())); + + mState = STATE_WAIT_FOR_ACTIVE_SOURCE; + addTimer(mState, HdmiConfig.TIMEOUT_MS); + return true; + } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + // The action finishes successfully if the <Active Source> message is received. + // {@link HdmiCecLocalDevice#onMessage} handles this message, so false is returned. + if (cmd.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) { + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + } + return false; + } + + @Override + void handleTimerEvent(int state) { + if (mState != state) { + return; + } + if (mState == STATE_WAIT_FOR_ACTIVE_SOURCE) { + finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); + } + } +} diff --git a/services/core/java/com/android/server/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/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 21284a05ddad..659c36c56047 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -45,11 +45,11 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.modules.utils.build.UnboundedSdkLevel; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.utils.TimingsTraceAndSlog; import com.google.android.collect.Lists; diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index 21610c9510ac..bdcec3a33221 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -58,6 +58,9 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.FgThread; @@ -68,9 +71,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java index d38b83fa6758..f3f64c5010ee 100644 --- a/services/core/java/com/android/server/pm/AppsFilterUtils.java +++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java @@ -27,15 +27,15 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.utils.WatchedArraySet; import com.android.server.utils.WatchedSparseSetArray; diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 26177037e1b4..5e76ae5cc2c3 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -123,6 +123,12 @@ import android.util.Xml; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.IndentingPrintWriter; @@ -141,12 +147,6 @@ import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.resolution.ComponentResolverApi; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index f8d27f1bad1c..d46d55977d2f 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -154,6 +154,11 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.F2fsUtils; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.security.VerityUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -177,11 +182,6 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedLibraryWrapper; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.rollback.RollbackManagerInternal; import com.android.server.security.FileIntegrityService; diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index e7499680b9a2..ea783b88cf64 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -47,13 +47,13 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.dex.DexManager; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import java.io.IOException; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 434c00afa8b2..7d3d85d33dcb 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -181,6 +181,8 @@ import com.android.internal.app.ResolverActivity; import com.android.internal.content.F2fsUtils; import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.om.OverlayConfig; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.telephony.CarrierAppUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -233,8 +235,6 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import com.android.server.pm.pkg.mutate.PackageStateWrite; import com.android.server.pm.pkg.mutate.PackageUserStateWrite; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index bcb7bdebf9bf..cd3416348153 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -92,6 +92,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.HexDump; @@ -104,7 +105,6 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.PackageDexUsage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.resolution.ComponentResolverApi; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; diff --git a/services/core/java/com/android/server/pm/PackageProperty.java b/services/core/java/com/android/server/pm/PackageProperty.java index 241f14390d5e..651bc5fac06a 100644 --- a/services/core/java/com/android/server/pm/PackageProperty.java +++ b/services/core/java/com/android/server/pm/PackageProperty.java @@ -31,8 +31,8 @@ import android.os.Binder; import android.os.UserHandle; import android.util.ArrayMap; +import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedComponent; import java.util.ArrayList; import java.util.Iterator; diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index b055a3ffd688..a8196f3f4985 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -44,6 +44,7 @@ import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.util.ArrayUtils; import com.android.server.pm.Installer.LegacyDexoptDisabledException; import com.android.server.pm.parsing.PackageCacher; @@ -52,7 +53,6 @@ import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import java.io.File; import java.util.Collections; diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 7ea9e3f529d0..22ee963cf9a2 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -73,6 +73,11 @@ import android.util.jar.StrictJarFile; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.parsing.PackageInfoUtils; @@ -82,11 +87,6 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedArraySet; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 6338965e4a80..7c969ef11666 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -89,6 +89,10 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.IndentingPrintWriter; @@ -113,10 +117,6 @@ import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.SuspendParams; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.resolution.ComponentResolver; import com.android.server.pm.verify.domain.DomainVerificationLegacySettings; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index 9376259c08b1..dddc6b0fbb7a 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -25,13 +25,13 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.proto.ProtoOutputStream; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.ArrayUtils; import com.android.server.pm.permission.LegacyPermissionState; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.pkg.component.ParsedProcessImpl; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; diff --git a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java index adac68b25749..215646778778 100644 --- a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java +++ b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java @@ -29,9 +29,9 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import org.xmlpull.v1.XmlPullParser; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 38cde3e3f7d7..91a70a6b4bdf 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -52,6 +52,17 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.PackageArchiver; @@ -65,17 +76,6 @@ import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.SELinuxUtil; import com.android.server.pm.pkg.component.ComponentParseUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedAttribution; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java index 97d526d1c44e..8916efd7aa5a 100644 --- a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java +++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java @@ -20,8 +20,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Pair; +import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedComponent; /** * For exposing internal fields to the rest of the server, enforcing that any overridden state from diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index e2acc17e94c4..0eb2bbde5886 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java @@ -29,16 +29,16 @@ import android.content.pm.parsing.result.ParseTypeImpl; import android.os.incremental.IncrementalManager; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.PackageManagerException; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.parsing.ParsingPackageHidden; import java.io.IOException; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java index 75dd67d97620..370d239ad329 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java @@ -50,6 +50,19 @@ import android.util.SparseIntArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; @@ -61,27 +74,15 @@ import com.android.server.pm.pkg.AndroidPackageSplit; import com.android.server.pm.pkg.AndroidPackageSplitImpl; import com.android.server.pm.pkg.SELinuxUtil; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl; -import com.android.server.pm.pkg.component.ParsedAttribution; import com.android.server.pm.pkg.component.ParsedAttributionImpl; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; import com.android.server.pm.pkg.component.ParsedPermissionImpl; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedProcessImpl; import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedServiceImpl; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageHidden; @@ -3305,7 +3306,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, this.instrumentations = ParsingUtils.createTypedInterfaceList(in, ParsedInstrumentationImpl.CREATOR); this.preferredActivityFilters = sForIntentInfoPairs.unparcel(in); - this.processes = in.readHashMap(ParsedProcess.class.getClassLoader()); + this.processes = in.readHashMap(ParsedProcessImpl.class.getClassLoader()); this.metaData = in.readBundle(boot); this.volumeUuid = sForInternedString.unparcel(in); this.signingDetails = in.readParcelable(boot, android.content.pm.SigningDetails.class); diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java index c81d6d7d0918..07ff0ee049e1 100644 --- a/services/core/java/com/android/server/pm/permission/Permission.java +++ b/services/core/java/com/android/server/pm/permission/Permission.java @@ -28,9 +28,9 @@ import android.os.UserHandle; import android.util.Log; import android.util.Slog; +import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.server.pm.PackageManagerService; import com.android.server.pm.pkg.PackageState; -import com.android.server.pm.pkg.component.ParsedPermission; import libcore.util.EmptyArray; diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 6764e087ff04..883b0666f979 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -119,6 +119,8 @@ import com.android.internal.compat.IPlatformCompat; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.RoSystemProperties; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.IntPair; @@ -142,8 +144,6 @@ import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionUtils; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.SoftRestrictedPermissionPolicy; diff --git a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java index 3a617041d55e..61677eb06fe9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java +++ b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java @@ -18,10 +18,11 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import android.util.ArrayMap; import android.util.ArraySet; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; + import java.util.Collection; /** diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java index 91854fda09d3..4d4efac4cbe8 100644 --- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -47,17 +47,17 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.R; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedApexSystemService; -import com.android.server.pm.pkg.component.ParsedAttribution; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.security.PublicKey; diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java index 1d2c5ec2d081..20fcdb564b96 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java @@ -22,7 +22,7 @@ import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.util.SparseArray; -import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedMainComponent; /** @hide */ public class PackageStateUtils { diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java index cd3583b814a4..fe80f743ffc3 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java @@ -27,7 +27,7 @@ import android.os.Debug; import android.util.DebugUtils; import android.util.Slog; -import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedMainComponent; /** @hide */ public class PackageUserStateUtils { diff --git a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java index 063f577bce5f..411bdede315f 100644 --- a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java +++ b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java @@ -22,8 +22,8 @@ import android.content.pm.SigningDetails; import android.util.ArrayMap; import android.util.ArraySet; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.server.pm.permission.LegacyPermissionState; -import com.android.server.pm.pkg.component.ParsedProcess; import java.util.List; diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java index 1deb8d055e20..1964df0853fd 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java @@ -19,6 +19,14 @@ package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; + /** * Contains mutation methods so that code doesn't have to cast to the Impl. Meant to eventually * be removed once all post-parsing mutation is moved to parsing. diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java index a8fb79a52837..041edaa98e63 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java @@ -29,6 +29,9 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.TextUtils; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.parsing.ParsingPackage; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java index 68d5428d6604..f02790189cc0 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java @@ -36,7 +36,7 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.ArraySet; -import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; import com.android.server.pm.pkg.parsing.ParsingUtils; @@ -49,7 +49,6 @@ import java.util.Set; * @hide **/ @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false) -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity, Parcelable { @@ -133,7 +132,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse * should be invisible to user and user should not know or see it. */ @NonNull - static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName, + public static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName, int uiOptions, String taskAffinity, boolean hardwareAccelerated) { ParsedActivityImpl activity = new ParsedActivityImpl(); activity.setPackageName(packageName); @@ -700,7 +699,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse time = 1669437519576L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java", - inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") + inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java index ee793c8b2f87..5709cbb09f93 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java @@ -48,6 +48,7 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.util.ArrayUtils; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java index 167aba301f35..cfed19aa0934 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -250,7 +251,7 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par time = 1643723578605L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java", - inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.server.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") + inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java index ed9aa2e6860a..d3fb29b8aa66 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java @@ -25,6 +25,8 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.TextUtils; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; + import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java index b59f511afa57..62b994724346 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java @@ -22,6 +22,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedAttribution; import com.android.internal.util.DataClass; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java index 98e94c5214f0..411220ae42e8 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java @@ -26,6 +26,7 @@ import android.content.res.XmlResourceParser; import android.util.ArraySet; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedAttribution; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java index f8d678ee39ac..512e5c7023c7 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java @@ -32,6 +32,8 @@ import android.text.TextUtils; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java index 8a0d356925d4..7bfad14d669a 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java @@ -26,6 +26,7 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java index c63a68975588..9792a91fb699 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java @@ -26,6 +26,7 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.parsing.ParsingPackage; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java index 5b6375d6b365..ab9404310078 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java @@ -23,6 +23,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.util.DataClass; /** diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java index 4f0a504b659f..5e67bbf4ab0b 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java @@ -31,6 +31,7 @@ import android.util.Slog; import android.util.TypedValue; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java index c670e7c1b4f7..f322eef8c3a3 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java @@ -25,6 +25,7 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java index f52ad1393878..6c22f825bab9 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java @@ -31,6 +31,8 @@ import android.os.Build; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java index 59075deb258c..afe37bc3274c 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.DataClass; /** @@ -174,7 +175,7 @@ public class ParsedPermissionGroupImpl extends ParsedComponentImpl implements time = 1642132854167L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java", - inputSignatures = "private int requestDetailRes\nprivate int backgroundRequestRes\nprivate int backgroundRequestDetailRes\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.server.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)") + inputSignatures = "private int requestDetailRes\nprivate int backgroundRequestRes\nprivate int backgroundRequestDetailRes\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java index 4c831d36c9e3..69e33c8f281e 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java @@ -24,6 +24,8 @@ import android.text.TextUtils; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -119,8 +121,8 @@ public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedP this.requestRes = in.readInt(); this.protectionLevel = in.readInt(); this.tree = in.readBoolean(); - this.parsedPermissionGroup = in.readParcelable(ParsedPermissionGroup.class.getClassLoader(), - ParsedPermissionGroupImpl.class); + this.parsedPermissionGroup = in.readParcelable( + ParsedPermissionGroupImpl.class.getClassLoader(), ParsedPermissionGroupImpl.class); this.knownCerts = sForStringSet.unparcel(in); } diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java index c6d1775307f8..0f2b49b8541c 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java @@ -31,6 +31,8 @@ import android.util.EventLog; import android.util.Slog; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java index 6d52f656b2e4..40e3670b9261 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java @@ -25,7 +25,7 @@ import android.os.Parcelable; import android.util.ArrayMap; import android.util.ArraySet; -import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -35,7 +35,6 @@ import java.util.Set; /** @hide */ @DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false, genBuilder = false) -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public class ParsedProcessImpl implements ParsedProcess, Parcelable { @NonNull diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java index 4f4c2d5019f3..766fb90cbfa0 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java @@ -27,6 +27,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.CollectionUtils; import com.android.internal.util.XmlUtils; import com.android.server.pm.pkg.parsing.ParsingPackage; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java index 6f4b4c84a07e..81a3c17e2bb4 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java @@ -28,6 +28,7 @@ import android.os.PatternMatcher; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -301,7 +302,7 @@ public class ParsedProviderImpl extends ParsedMainComponentImpl implements Parse time = 1642560323360L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java", - inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)") + inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java index 37bed15ba1d7..b66db4f9ced4 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java @@ -34,6 +34,7 @@ import android.os.PatternMatcher; import android.util.Slog; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java index 47e993cb02be..ca8c45d1383c 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java @@ -26,6 +26,8 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java index c15266fc4cbc..1b421841f166 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java @@ -32,6 +32,7 @@ import android.content.res.XmlResourceParser; import android.os.Build; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java index 9b89373bbb90..78377a836651 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java index 699ccbdc5a83..408a531eaf1d 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java @@ -32,18 +32,18 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedApexSystemService; -import com.android.server.pm.pkg.component.ParsedAttribution; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import java.security.PublicKey; import java.util.List; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 061698a929d6..417e3ae6e7df 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -89,6 +89,19 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.R; import com.android.internal.os.ClassLoaderFactory; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import com.android.server.pm.SharedUidMigration; @@ -98,29 +111,17 @@ import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.component.ComponentMutateUtils; import com.android.server.pm.pkg.component.ComponentParseUtils; import com.android.server.pm.pkg.component.InstallConstraintsTagParser; -import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityImpl; import com.android.server.pm.pkg.component.ParsedActivityUtils; -import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.pm.pkg.component.ParsedApexSystemServiceUtils; -import com.android.server.pm.pkg.component.ParsedAttribution; import com.android.server.pm.pkg.component.ParsedAttributionUtils; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedInstrumentationUtils; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; import com.android.server.pm.pkg.component.ParsedIntentInfoUtils; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionUtils; -import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.pkg.component.ParsedProcessUtils; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedProviderUtils; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedServiceUtils; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.pm.split.DefaultSplitAssetLoader; import com.android.server.pm.split.SplitAssetDependencyLoader; @@ -2842,7 +2843,7 @@ public class ParsingPackageUtils { String taskAffinity = result.getResult(); // Build custom App Details activity info instead of parsing it from xml - return input.success(ParsedActivity.makeAppDetailsActivity(packageName, + return input.success(ParsedActivityImpl.makeAppDetailsActivity(packageName, pkg.getProcessName(), pkg.getUiOptions(), taskAffinity, pkg.isHardwareAccelerated())); } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java index 07512855d276..2cfffb3b185d 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java @@ -30,9 +30,9 @@ import android.os.Parcelable; import android.util.Pair; import android.util.Slog; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.util.Parcelling; import com.android.internal.util.XmlUtils; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java index 0ceda421913d..ed6d3b94758b 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java @@ -45,6 +45,12 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.server.IntentResolver; import com.android.server.pm.Computer; @@ -57,13 +63,7 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.snapshot.PackageDataSnapshot; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java index b8e4c8d2a51f..0f12ee1b52fb 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java @@ -25,11 +25,11 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.Computer; import com.android.server.pm.DumpState; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import java.io.PrintWriter; import java.util.List; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java index 80cde73ecf1f..2bc926c6d840 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java @@ -28,6 +28,11 @@ import android.os.UserHandle; import android.util.ArrayMap; import android.util.Pair; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.Computer; import com.android.server.pm.DumpState; import com.android.server.pm.UserManagerService; @@ -36,11 +41,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.utils.WatchableImpl; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java index 0c84f4c53dfe..add33b25923e 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java @@ -24,13 +24,13 @@ import android.content.Intent; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.Computer; import com.android.server.pm.DumpState; import com.android.server.pm.PackageManagerTracedLock; import com.android.server.pm.UserManagerService; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import java.io.PrintWriter; import java.util.List; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index adef808bd712..735f90faee5b 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -27,11 +27,11 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Patterns; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.server.SystemConfig; import com.android.server.compat.PlatformCompat; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import java.util.List; import java.util.Objects; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 3d4d4eca7f48..6150099b2945 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -48,6 +48,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.util.CollectionUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -60,7 +61,6 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.PackageUserStateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; diff --git a/services/core/java/com/android/server/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/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 26f0d34a6261..7b399c837d32 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1018,9 +1018,8 @@ class ActivityClientController extends IActivityClientController.Stub { } try { - final ClientTransaction transaction = ClientTransaction.obtain(r.app.getThread()); - transaction.addCallback(EnterPipRequestedItem.obtain(r.token)); - mService.getLifecycleManager().scheduleTransaction(transaction); + mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), + EnterPipRequestedItem.obtain(r.token)); return true; } catch (Exception e) { Slog.w(TAG, "Failed to send enter pip requested item: " diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index eeeca1018b0a..24d99387d63c 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8185,6 +8185,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * aspect ratio. */ boolean shouldCreateCompatDisplayInsets() { + if (mLetterboxUiController.shouldApplyUserFullscreenOverride()) { + // If the user has forced the applications aspect ratio to be fullscreen, don't use size + // compatibility mode in any situation. The user has been warned and therefore accepts + // the risk of the application misbehaving. + return false; + } switch (supportsSizeChanges()) { case SIZE_CHANGES_SUPPORTED_METADATA: case SIZE_CHANGES_SUPPORTED_OVERRIDE: diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 777b5cd4337b..e196d463db79 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -936,7 +936,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { final int deviceId = getDeviceIdForDisplayId(r.getDisplayId()); clientTransaction.addCallback(LaunchActivityItem.obtain(r.token, - new Intent(r.intent), System.identityHashCode(r), r.info, + r.intent, System.identityHashCode(r), r.info, // TODO: Have this take the merged configuration instead of separate global // and override configs. mergedConfiguration.getGlobalConfiguration(), diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index a2f5a383caad..c2b5f88d0b4f 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -583,7 +583,7 @@ public class BackgroundActivityStartController { + " if the PI sender upgrades target_sdk to 34+! " + " (missing opt in by PI sender)! " + state.dump(resultForCaller, resultForRealCaller)); - showBalBlockedToast("BAL would be blocked", state); + showBalRiskToast("BAL would be blocked", state); return statsLog(resultForRealCaller, state); } Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed" diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 735cbc4e4287..5518de7b64fd 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -254,7 +254,9 @@ final class LetterboxUiController { // Counter for ActivityRecord#setRequestedOrientation private int mSetOrientationRequestCounter = 0; - // The min aspect ratio override set by user + // The min aspect ratio override set by user. Stores the last selected aspect ratio after + // {@link #shouldApplyUserFullscreenOverride} or {@link #shouldApplyUserMinAspectRatioOverride} + // have been invoked. @PackageManager.UserMinAspectRatio private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET; @@ -661,7 +663,9 @@ final class LetterboxUiController { @ScreenOrientation int overrideOrientationIfNeeded(@ScreenOrientation int candidate) { - if (shouldApplyUserFullscreenOverride()) { + if (shouldApplyUserFullscreenOverride() + && mActivityRecord.mDisplayContent != null + && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) { Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + mActivityRecord + " is overridden to " + screenOrientationToString(SCREEN_ORIENTATION_USER) @@ -1171,9 +1175,7 @@ final class LetterboxUiController { boolean shouldApplyUserFullscreenOverride() { if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride) || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride) - || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled() - || mActivityRecord.mDisplayContent == null - || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) { + || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) { return false; } diff --git a/services/core/java/com/android/server/wm/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/proguard.flags b/services/proguard.flags index 261bb7cacdc4..407505d6eda0 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -14,13 +14,20 @@ } # APIs referenced by dependent JAR files and modules --keep @interface android.annotation.SystemApi +# TODO(b/300514883): Pull @SystemApi keep rules from system-api.pro. +-keep interface android.annotation.SystemApi -keep @android.annotation.SystemApi class * { public protected *; } -keepclasseswithmembers class * { @android.annotation.SystemApi *; } +# Also ensure nested classes are kept. This is overly conservative, but handles +# cases where such classes aren't explicitly marked @SystemApi. +-if @android.annotation.SystemApi class * +-keep public class <1>$** { + public protected *; +} # Derivatives of SystemService and other services created via reflection -keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService { diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index 12cd0f6e1a7c..8d76fddcc793 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -24,6 +24,7 @@ import android.content.pm.PackageManager import android.os.Binder import android.os.UserHandle import android.util.ArrayMap +import com.android.internal.pm.pkg.component.ParsedActivity import com.android.server.pm.AppsFilterImpl import com.android.server.pm.PackageManagerService import com.android.server.pm.PackageManagerServiceInjector @@ -39,7 +40,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackageInternal import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.parsing.pkg.ParsedPackage import com.android.server.pm.pkg.AndroidPackage -import com.android.server.pm.pkg.component.ParsedActivity import com.android.server.pm.resolution.ComponentResolver import com.android.server.pm.snapshot.PackageDataSnapshot import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java index d5cd6ef9eb69..25146a87970f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java @@ -49,16 +49,16 @@ import android.util.SparseArray; import androidx.annotation.NonNull; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.server.om.OverlayReferenceMapper; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedActivityImpl; import com.android.server.pm.pkg.component.ParsedComponentImpl; import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; -import com.android.server.pm.pkg.component.ParsedPermission; import com.android.server.pm.pkg.component.ParsedPermissionImpl; import com.android.server.pm.pkg.component.ParsedProviderImpl; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index 0eac4e6a25ac..7c28e13f0eee 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -58,6 +58,16 @@ import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageInfoUtils; @@ -69,24 +79,14 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedApexSystemService; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; import com.android.server.pm.pkg.component.ParsedPermissionImpl; import com.android.server.pm.pkg.component.ParsedPermissionUtils; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedServiceImpl; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.pm.pkg.parsing.ParsingPackage; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index 9e371649214c..7123c2076640 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -38,16 +38,16 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.server.pm.test.service.server.R; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.util.ArrayUtils; import com.android.server.pm.PackageManagerException; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.component.ParsedActivityUtils; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; import com.android.server.pm.pkg.component.ParsedPermissionUtils; +import com.android.server.pm.test.service.server.R; import com.google.common.truth.Expect; diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt index 0e2e35f25b15..26468544e8d6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.ActivityInfo -import com.android.server.pm.pkg.component.ParsedActivity +import com.android.internal.pm.pkg.component.ParsedActivity import com.android.server.pm.pkg.component.ParsedActivityImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt index 4e44e96aa710..52d5b3bccb72 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedAttribution +import com.android.internal.pm.pkg.component.ParsedAttribution import com.android.server.pm.pkg.component.ParsedAttributionImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt index 058f6d69f3e7..af0c0de2db15 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PackageManager -import com.android.server.pm.pkg.component.ParsedComponent +import com.android.internal.pm.pkg.component.ParsedComponent import com.android.server.pm.pkg.component.ParsedComponentImpl import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Bundle diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt index eeb30b70c143..dc0f194f10cc 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedInstrumentation +import com.android.internal.pm.pkg.component.ParsedInstrumentation import com.android.server.pm.pkg.component.ParsedInstrumentationImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt index f27a51f63049..5224f23d38d1 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedIntentInfo +import com.android.internal.pm.pkg.component.ParsedIntentInfo import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Parcelable import android.os.PatternMatcher diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt index a0d8c44899d8..dfff6025e2eb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedMainComponent +import com.android.internal.pm.pkg.component.ParsedMainComponent import com.android.server.pm.pkg.component.ParsedMainComponentImpl import android.os.Parcelable import java.util.Arrays diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt index f266e7616ff3..ccbf558734d3 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.internal.pm.pkg.component.ParsedPermissionGroup import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt index c72a44e4c4e0..2814783b6849 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedPermission -import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.internal.pm.pkg.component.ParsedPermission +import com.android.internal.pm.pkg.component.ParsedPermissionGroup import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl import com.android.server.pm.pkg.component.ParsedPermissionImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt index 8b9361a31d0a..2e9604696acb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedProcess +import com.android.internal.pm.pkg.component.ParsedProcess import com.android.server.pm.pkg.component.ParsedProcessImpl import android.util.ArrayMap import kotlin.contracts.ExperimentalContracts @@ -45,7 +45,7 @@ class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class, ParsedPr override fun extraParams() = listOf( getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission")), getter(ParsedProcess::getAppClassNamesByPackage, ArrayMap<String, String>().apply { - put("package1", "classname1"); + put("package1", "classname1") }), ) } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt index 0302d5785d8f..290dbd6277b6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt @@ -17,14 +17,14 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PathPermission -import com.android.server.pm.pkg.component.ParsedProvider +import com.android.internal.pm.pkg.component.ParsedProvider import com.android.server.pm.pkg.component.ParsedProviderImpl import android.os.PatternMatcher import kotlin.contracts.ExperimentalContracts @ExperimentalContracts -class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class) { - +class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class) +{ override val defaultImpl = ParsedProviderImpl() override val creator = ParsedProviderImpl.CREATOR diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt index e2c9439df9cf..3ae7e9220cc6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedService +import com.android.internal.pm.pkg.component.ParsedService import com.android.server.pm.pkg.component.ParsedServiceImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt index ad607366967f..67dfc6d4f7ef 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedUsesPermission +import com.android.internal.pm.pkg.component.ParsedUsesPermission import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt index d217d63c5b44..1da3a2234ffc 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt @@ -24,26 +24,25 @@ import android.content.pm.SharedLibraryInfo import android.content.pm.VersionedPackage import android.os.PatternMatcher import android.util.ArraySet +import com.android.internal.pm.pkg.component.ParsedActivity +import com.android.internal.pm.pkg.component.ParsedInstrumentation +import com.android.internal.pm.pkg.component.ParsedPermission +import com.android.internal.pm.pkg.component.ParsedPermissionGroup +import com.android.internal.pm.pkg.component.ParsedProcess +import com.android.internal.pm.pkg.component.ParsedProvider +import com.android.internal.pm.pkg.component.ParsedService import com.android.server.pm.PackageSetting import com.android.server.pm.PackageSettingBuilder import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.pm.pkg.PackageUserState -import com.android.server.pm.pkg.PackageUserStateImpl -import com.android.server.pm.pkg.component.ParsedActivity import com.android.server.pm.pkg.component.ParsedActivityImpl import com.android.server.pm.pkg.component.ParsedComponentImpl -import com.android.server.pm.pkg.component.ParsedInstrumentation import com.android.server.pm.pkg.component.ParsedIntentInfoImpl -import com.android.server.pm.pkg.component.ParsedPermission -import com.android.server.pm.pkg.component.ParsedPermissionGroup import com.android.server.pm.pkg.component.ParsedPermissionImpl -import com.android.server.pm.pkg.component.ParsedProcess import com.android.server.pm.pkg.component.ParsedProcessImpl -import com.android.server.pm.pkg.component.ParsedProvider import com.android.server.pm.pkg.component.ParsedProviderImpl -import com.android.server.pm.pkg.component.ParsedService import com.android.server.pm.test.parsing.parcelling.AndroidPackageTest import com.google.common.truth.Expect import org.junit.Rule diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt index ec84bc329674..316f3387c539 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt @@ -26,6 +26,8 @@ import android.os.Bundle import android.util.ArrayMap import android.util.SparseArray import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.internal.pm.pkg.component.ParsedPermission +import com.android.internal.pm.pkg.component.ParsedPermissionGroup import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.server.extendedtestutils.wheneverStatic import com.android.server.permission.access.MutableAccessState @@ -39,8 +41,6 @@ import com.android.server.pm.parsing.PackageInfoUtils import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.pm.pkg.PackageUserState -import com.android.server.pm.pkg.component.ParsedPermission -import com.android.server.pm.pkg.component.ParsedPermissionGroup import com.android.server.testutils.any import com.android.server.testutils.mock import com.android.server.testutils.whenever diff --git a/services/tests/displayservicetests/src/com/android/server/display/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/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 cdff62320918..30300ec3ad2e 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -52,9 +52,11 @@ import android.Manifest; import android.app.WindowConfiguration; import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; +import android.companion.AssociationRequest; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.IVirtualDeviceIntentInterceptor; import android.companion.virtual.IVirtualDeviceSoundEffectListener; +import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; @@ -134,6 +136,8 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -261,6 +265,8 @@ public class VirtualDeviceManagerServiceTest { @Mock private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener mAppsOnVirtualDeviceListener; @Mock + private Consumer<String> mPersistentDeviceIdRemovedListener; + @Mock IPowerManager mIPowerManagerMock; @Mock IThermalService mIThermalServiceMock; @@ -372,9 +378,8 @@ public class VirtualDeviceManagerServiceTest { mCameraAccessController = new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback); - mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null, - null, MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, - 0, 0, -1); + mAssociationInfo = createAssociationInfo( + /* associationId= */ 1, AssociationRequest.DEVICE_PROFILE_APP_STREAMING); mVdms = new VirtualDeviceManagerService(mContext); mLocalService = mVdms.getLocalServiceInstance(); @@ -722,6 +727,39 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void onPersistentDeviceIdsRemoved_listenersNotified() { + mLocalService.registerPersistentDeviceIdRemovedListener(mPersistentDeviceIdRemovedListener); + mLocalService.onPersistentDeviceIdsRemoved(Set.of(mDeviceImpl.getPersistentDeviceId())); + TestableLooper.get(this).processAllMessages(); + + verify(mPersistentDeviceIdRemovedListener).accept(mDeviceImpl.getPersistentDeviceId()); + } + + @Test + public void onCdmAssociationsChanged_persistentDeviceIdRemovedListenersNotified() { + mLocalService.registerPersistentDeviceIdRemovedListener(mPersistentDeviceIdRemovedListener); + mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo)); + TestableLooper.get(this).processAllMessages(); + + mVdms.onCdmAssociationsChanged(List.of( + createAssociationInfo(2, AssociationRequest.DEVICE_PROFILE_APP_STREAMING), + createAssociationInfo(3, AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION), + createAssociationInfo(4, AssociationRequest.DEVICE_PROFILE_WATCH))); + TestableLooper.get(this).processAllMessages(); + + verify(mPersistentDeviceIdRemovedListener).accept(mDeviceImpl.getPersistentDeviceId()); + + mVdms.onCdmAssociationsChanged(Collections.emptyList()); + TestableLooper.get(this).processAllMessages(); + + verify(mPersistentDeviceIdRemovedListener) + .accept(VirtualDeviceImpl.createPersistentDeviceId(2)); + verify(mPersistentDeviceIdRemovedListener) + .accept(VirtualDeviceImpl.createPersistentDeviceId(3)); + verifyNoMoreInteractions(mPersistentDeviceIdRemovedListener); + } + + @Test public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() { ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2)); mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener); @@ -1860,11 +1898,16 @@ public class VirtualDeviceManagerServiceTest { @Test public void getPersistentIdForDevice_invalidDeviceId_returnsNull() { assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_INVALID)).isNull(); - assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_DEFAULT)).isNull(); assertThat(mLocalService.getPersistentIdForDevice(VIRTUAL_DEVICE_ID_2)).isNull(); } @Test + public void getPersistentIdForDevice_defaultDeviceId() { + assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_DEFAULT)).isEqualTo( + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + } + + @Test public void getPersistentIdForDevice_returnsCorrectId() { assertThat(mLocalService.getPersistentIdForDevice(VIRTUAL_DEVICE_ID_1)) .isEqualTo(mDeviceImpl.getPersistentDeviceId()); @@ -1897,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()) @@ -1919,6 +1962,14 @@ public class VirtualDeviceManagerServiceTest { return intent.resolveActivity(packageManager); } + private AssociationInfo createAssociationInfo(int associationId, String deviceProfile) { + return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null, + /* tag= */ null, MacAddress.BROADCAST_ADDRESS, /* displayName= */ "", deviceProfile, + /* associatedDevice= */ null, /* selfManaged= */ true, + /* notifyOnDeviceNearby= */ false, /* revoked= */false, /* timeApprovedMs= */0, + /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1); + } + /** Helper class to drop permissions temporarily and restore them at the end of a test. */ static final class DropShellPermissionsTemporarily implements AutoCloseable { DropShellPermissionsTemporarily() { diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java deleted file mode 100644 index dbd6c889622a..000000000000 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.virtual; - -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyFloat; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; - -import android.app.admin.DevicePolicyManager; -import android.companion.AssociationInfo; -import android.companion.virtual.IVirtualDeviceActivityListener; -import android.companion.virtual.IVirtualDeviceSoundEffectListener; -import android.companion.virtual.VirtualDeviceParams; -import android.companion.virtual.flags.Flags; -import android.content.AttributionSource; -import android.content.Context; -import android.hardware.display.DisplayManagerGlobal; -import android.hardware.display.DisplayManagerInternal; -import android.hardware.display.IDisplayManager; -import android.net.MacAddress; -import android.os.Binder; -import android.testing.TestableContext; -import android.util.ArraySet; -import android.view.Display; -import android.view.DisplayInfo; -import android.view.WindowManager; - -import androidx.annotation.NonNull; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.android.server.LocalServices; -import com.android.server.companion.virtual.camera.VirtualCameraController; -import com.android.server.input.InputManagerInternal; -import com.android.server.sensors.SensorManagerInternal; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Supplier; - -/** Test rule to generate instances of {@link VirtualDeviceImpl}. */ -public class VirtualDeviceRule implements TestRule { - - private static final int DEVICE_OWNER_UID = 50; - private static final int VIRTUAL_DEVICE_ID = 42; - - private final Context mContext; - private InputController mInputController; - private CameraAccessController mCameraAccessController; - private AssociationInfo mAssociationInfo; - private VirtualDeviceManagerService mVdms; - private VirtualDeviceManagerInternal mLocalService; - private VirtualDeviceLog mVirtualDeviceLog; - - // Mocks - @Mock private InputController.NativeWrapper mNativeWrapperMock; - @Mock private DisplayManagerInternal mDisplayManagerInternalMock; - @Mock private IDisplayManager mIDisplayManager; - @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; - @Mock private DevicePolicyManager mDevicePolicyManagerMock; - @Mock private InputManagerInternal mInputManagerInternalMock; - @Mock private SensorManagerInternal mSensorManagerInternalMock; - @Mock private IVirtualDeviceActivityListener mActivityListener; - @Mock private IVirtualDeviceSoundEffectListener mSoundEffectListener; - @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; - @Mock private CameraAccessController.CameraAccessBlockedCallback mCameraAccessBlockedCallback; - - // Test instance suppliers - private Supplier<VirtualCameraController> mVirtualCameraControllerSupplier; - - /** - * Create a new {@link VirtualDeviceRule} - * - * @param context The context to be used with the rule. - */ - public VirtualDeviceRule(@NonNull Context context) { - Objects.requireNonNull(context); - mContext = context; - } - - /** - * Sets a supplier that will supply an instance of {@link VirtualCameraController}. If the - * supplier returns null, a new instance will be created. - */ - public VirtualDeviceRule withVirtualCameraControllerSupplier( - Supplier<VirtualCameraController> virtualCameraControllerSupplier) { - mVirtualCameraControllerSupplier = virtualCameraControllerSupplier; - return this; - } - - @Override - public Statement apply(Statement base, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - init(new TestableContext(mContext)); - base.evaluate(); - } - }; - } - - private void init(@NonNull TestableContext context) { - MockitoAnnotations.initMocks(this); - - LocalServices.removeServiceForTest(DisplayManagerInternal.class); - LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - - doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); - doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); - doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt()); - LocalServices.removeServiceForTest(InputManagerInternal.class); - LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); - - LocalServices.removeServiceForTest(SensorManagerInternal.class); - LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock); - - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.uniqueId = "uniqueId"; - doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt()); - doReturn(Display.INVALID_DISPLAY).when(mDisplayManagerInternalMock) - .getDisplayIdToMirror(anyInt()); - LocalServices.removeServiceForTest(DisplayManagerInternal.class); - LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - - context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManagerMock); - - // Allow virtual devices to be created on the looper thread for testing. - final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true; - mInputController = - new InputController( - mNativeWrapperMock, - InstrumentationRegistry.getInstrumentation() - .getContext() - .getMainThreadHandler(), - context.getSystemService(WindowManager.class), - threadVerifier); - mCameraAccessController = - new CameraAccessController(context, mLocalService, mCameraAccessBlockedCallback); - - mAssociationInfo = - new AssociationInfo( - /* associationId= */ 1, - 0, - null, - null, - MacAddress.BROADCAST_ADDRESS, - "", - null, - null, - true, - false, - false, - 0, - 0, - -1); - - mVdms = new VirtualDeviceManagerService(context); - mLocalService = mVdms.getLocalServiceInstance(); - mVirtualDeviceLog = new VirtualDeviceLog(context); - } - - /** - * Create a {@link VirtualDeviceImpl} with the required mocks - * - * @param params See {@link - * android.companion.virtual.VirtualDeviceManager#createVirtualDevice(int, - * VirtualDeviceParams)} - */ - public VirtualDeviceImpl createVirtualDevice(VirtualDeviceParams params) { - VirtualCameraController virtualCameraController = mVirtualCameraControllerSupplier.get(); - if (Flags.virtualCamera()) { - if (virtualCameraController == null) { - virtualCameraController = new VirtualCameraController(mContext); - } - } - - VirtualDeviceImpl virtualDeviceImpl = - new VirtualDeviceImpl( - mContext, - mAssociationInfo, - mVdms, - mVirtualDeviceLog, - new Binder(), - new AttributionSource( - DEVICE_OWNER_UID, - "com.android.virtualdevice.test", - "virtualdevicerule"), - VIRTUAL_DEVICE_ID, - mInputController, - mCameraAccessController, - mPendingTrampolineCallback, - mActivityListener, - mSoundEffectListener, - mRunningAppsChangedCallback, - params, - new DisplayManagerGlobal(mIDisplayManager), - virtualCameraController); - mVdms.addVirtualDevice(virtualDeviceImpl); - return virtualDeviceImpl; - } -} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java new file mode 100644 index 000000000000..258302354e45 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual.camera; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.companion.virtual.camera.VirtualCameraCallback; +import android.companion.virtual.camera.VirtualCameraConfig; +import android.companion.virtual.camera.VirtualCameraMetadata; +import android.companion.virtual.camera.VirtualCameraStreamConfig; +import android.companion.virtualcamera.IVirtualCameraService; +import android.companion.virtualcamera.VirtualCameraConfiguration; +import android.graphics.ImageFormat; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Looper; +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.Surface; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@Presubmit +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class VirtualCameraControllerTest { + + private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10; + private static final int CAMERA_WIDTH_1 = 100; + private static final int CAMERA_HEIGHT_1 = 200; + private static final int CAMERA_FORMAT_1 = ImageFormat.RGB_565; + + private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11; + private static final int CAMERA_WIDTH_2 = 400; + private static final int CAMERA_HEIGHT_2 = 600; + private static final int CAMERA_FORMAT_2 = ImageFormat.YUY2; + + @Mock + private IVirtualCameraService mVirtualCameraServiceMock; + + private VirtualCameraController mVirtualCameraController; + private final HandlerExecutor mCallbackHandler = + new HandlerExecutor(new Handler(Looper.getMainLooper())); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mVirtualCameraController = new VirtualCameraController(mVirtualCameraServiceMock); + when(mVirtualCameraServiceMock.registerCamera(any(), any())).thenReturn(true); + } + + @Test + public void registerCamera_registersCamera() throws Exception { + mVirtualCameraController.registerCamera(createVirtualCameraConfig( + CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1)); + + ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor = + ArgumentCaptor.forClass(VirtualCameraConfiguration.class); + verify(mVirtualCameraServiceMock).registerCamera(any(), configurationCaptor.capture()); + VirtualCameraConfiguration virtualCameraConfiguration = configurationCaptor.getValue(); + assertThat(virtualCameraConfiguration.supportedStreamConfigs.length).isEqualTo(1); + assertVirtualCameraConfiguration(virtualCameraConfiguration, CAMERA_WIDTH_1, + CAMERA_HEIGHT_1, CAMERA_FORMAT_1); + } + + @Test + public void unregisterCamera_unregistersCamera() throws Exception { + VirtualCameraConfig config = createVirtualCameraConfig( + CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1); + mVirtualCameraController.unregisterCamera(config); + + verify(mVirtualCameraServiceMock).unregisterCamera(any()); + } + + @Test + public void close_unregistersAllCameras() throws Exception { + mVirtualCameraController.registerCamera(createVirtualCameraConfig( + CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1)); + mVirtualCameraController.registerCamera(createVirtualCameraConfig( + CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_DISPLAY_NAME_RES_ID_2)); + + ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor = + ArgumentCaptor.forClass(VirtualCameraConfiguration.class); + mVirtualCameraController.close(); + verify(mVirtualCameraServiceMock, times(2)).registerCamera(any(), + configurationCaptor.capture()); + List<VirtualCameraConfiguration> virtualCameraConfigurations = + configurationCaptor.getAllValues(); + assertThat(virtualCameraConfigurations).hasSize(2); + assertVirtualCameraConfiguration(virtualCameraConfigurations.get(0), CAMERA_WIDTH_1, + CAMERA_HEIGHT_1, CAMERA_FORMAT_1); + assertVirtualCameraConfiguration(virtualCameraConfigurations.get(1), CAMERA_WIDTH_2, + CAMERA_HEIGHT_2, CAMERA_FORMAT_2); + } + + private VirtualCameraConfig createVirtualCameraConfig( + int width, int height, int format, int displayNameResId) { + return new VirtualCameraConfig.Builder() + .addStreamConfig(width, height, format) + .setDisplayNameStringRes(displayNameResId) + .setVirtualCameraCallback(mCallbackHandler, createNoOpCallback()) + .build(); + } + + private static void assertVirtualCameraConfiguration( + VirtualCameraConfiguration configuration, int width, int height, int format) { + assertThat(configuration.supportedStreamConfigs[0].width).isEqualTo(width); + assertThat(configuration.supportedStreamConfigs[0].height).isEqualTo(height); + assertThat(configuration.supportedStreamConfigs[0].pixelFormat).isEqualTo(format); + } + + private static VirtualCameraCallback createNoOpCallback() { + return new VirtualCameraCallback() { + + @Override + public void onStreamConfigured( + int streamId, + @NonNull Surface surface, + @NonNull VirtualCameraStreamConfig streamConfig) {} + + @Override + public void onProcessCaptureRequest( + int streamId, long frameId, @Nullable VirtualCameraMetadata metadata) {} + + @Override + public void onStreamClosed(int streamId) {} + }; + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index c632727fd420..9e5bea7d135b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -1680,4 +1680,47 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mHdmiControlService.isSystemAudioActivated()).isTrue(); } + + @Test + public void onAddressAllocated_startRequestActiveSourceAction_playbackActiveSource() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + HdmiCecMessage activeSourceFromPlayback = + HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x1000); + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + + mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + mNativeWrapper.clearResultMessages(); + mNativeWrapper.onCecMessage(activeSourceFromPlayback); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); + } + + @Test + public void onAddressAllocated_startRequestActiveSourceAction_noActiveSource() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + + mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java new file mode 100644 index 000000000000..8dcf89bb8438 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.service.notification.ZenDeviceEffects; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.UiServiceTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class ZenDeviceEffectsTest extends UiServiceTestCase { + + @Test + public void builder() { + ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisableTapToWake(true).setShouldDisableTapToWake(false) + .setShouldDisableTiltToWake(true) + .setShouldMaximizeDoze(true) + .setShouldUseNightMode(false) + .setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true) + .build(); + + assertThat(deviceEffects.shouldDimWallpaper()).isTrue(); + assertThat(deviceEffects.shouldDisableAutoBrightness()).isFalse(); + assertThat(deviceEffects.shouldDisableTapToWake()).isFalse(); + assertThat(deviceEffects.shouldDisableTiltToWake()).isTrue(); + assertThat(deviceEffects.shouldDisableTouch()).isFalse(); + assertThat(deviceEffects.shouldDisplayGrayscale()).isFalse(); + assertThat(deviceEffects.shouldMaximizeDoze()).isTrue(); + assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse(); + assertThat(deviceEffects.shouldUseNightMode()).isFalse(); + assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue(); + } + + @Test + public void builder_fromInstance() { + ZenDeviceEffects original = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisableTiltToWake(true) + .setShouldUseNightMode(true) + .setShouldSuppressAmbientDisplay(true) + .build(); + + ZenDeviceEffects modified = new ZenDeviceEffects.Builder(original) + .setShouldDisplayGrayscale(true) + .setShouldUseNightMode(false) + .build(); + + assertThat(modified.shouldDimWallpaper()).isTrue(); // from original + assertThat(modified.shouldDisableTiltToWake()).isTrue(); // from original + assertThat(modified.shouldDisplayGrayscale()).isTrue(); // updated + assertThat(modified.shouldUseNightMode()).isFalse(); // updated + assertThat(modified.shouldSuppressAmbientDisplay()).isTrue(); // from original + } + + @Test + public void writeToParcel_parcelsAndUnparcels() { + ZenDeviceEffects source = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisableTouch(true) + .setShouldMinimizeRadioUsage(true) + .setShouldUseNightMode(true) + .setShouldSuppressAmbientDisplay(true) + .build(); + + Parcel parcel = Parcel.obtain(); + ZenDeviceEffects copy; + try { + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + copy = ZenDeviceEffects.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + + assertThat(copy.shouldDimWallpaper()).isTrue(); + assertThat(copy.shouldDisableTouch()).isTrue(); + assertThat(copy.shouldMinimizeRadioUsage()).isTrue(); + assertThat(copy.shouldUseNightMode()).isTrue(); + assertThat(copy.shouldSuppressAmbientDisplay()).isTrue(); + assertThat(copy.shouldDisplayGrayscale()).isFalse(); + } +} diff --git a/services/tests/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/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 0d4c443ce1b0..c6796dc9e90d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -787,22 +787,44 @@ public class LetterboxUiControllerTest extends WindowTestsBase { public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() { spyOn(mController); doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(true); assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); } + @Test + public void testOverrideOrientationIfNeeded_respectOrientationRequestOverUserFullScreen() { + spyOn(mController); + doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(false); + + assertNotEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); + } @Test @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) public void testOverrideOrientationIfNeeded_userFullScreenOverrideOverSystem_returnsUser() { spyOn(mController); doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(true); assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); } @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_respectOrientationReqOverUserFullScreenAndSystem() { + spyOn(mController); + doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(false); + + assertNotEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); + } + + @Test public void testOverrideOrientationIfNeeded_userFullScreenOverrideDisabled_returnsUnchanged() { spyOn(mController); doReturn(false).when(mController).shouldApplyUserFullscreenOverride(); @@ -872,14 +894,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test - public void testShouldApplyUserFullscreenOverride_disabledIgnoreOrientationRequest() { - prepareActivityThatShouldApplyUserFullscreenOverride(); - mDisplayContent.setIgnoreOrientationRequest(false); - - assertFalse(mController.shouldApplyUserFullscreenOverride()); - } - - @Test public void testShouldApplyUserFullscreenOverride_returnsTrue() { prepareActivityThatShouldApplyUserFullscreenOverride(); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 491d5b56c8e2..8de45b039f62 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -202,8 +202,7 @@ public class RecentsAnimationTest extends WindowTestsBase { any() /* starting */, anyInt() /* configChanges */, anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt()); - ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager(); - doNothing().when(lifecycleManager).scheduleTransaction(any()); + doNothing().when(mClientLifecycleManager).scheduleTransaction(any()); startRecentsActivity(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index a7c14c38b832..c3102e08489d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -31,6 +31,7 @@ import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -1301,6 +1302,27 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testShouldCreateCompatDisplayUserAspectRatioFullscreenOverride() { + setUpDisplaySizeWithApp(1000, 2500); + + // Make the task root resizable. + mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE; + + // Create an activity on the same task. + final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false, + RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + + // Simulate the user selecting the fullscreen user aspect ratio override + spyOn(activity.mWmService.mLetterboxConfiguration); + spyOn(activity.mLetterboxUiController); + doReturn(true).when(activity.mWmService.mLetterboxConfiguration) + .isUserAppAspectRatioFullscreenEnabled(); + doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(activity.mLetterboxUiController) + .getUserMinAspectRatioOverrideCode(); + assertFalse(activity.shouldCreateCompatDisplayInsets()); + } + + @Test @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS}) public void testNeverSandboxDisplayApis_configEnabled_sandboxingNotApplied() { setUpDisplaySizeWithApp(1000, 1200); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java index 4a335944228a..6655932b060b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -31,7 +31,9 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -143,6 +145,15 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { secureWindow.mAttrs.flags |= FLAG_SECURE; assertEquals(SNAPSHOT_MODE_APP_THEME, mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask())); + + // Verifies that if the snapshot can be cached, then getSnapshotMode should be respected. + // Otherwise a real snapshot can be taken even if the activity disables recents screenshot. + spyOn(mWm.mTaskSnapshotController); + final int disabledInRecentsTaskId = disabledWindow.getTask().mTaskId; + mAtm.takeTaskSnapshot(disabledInRecentsTaskId, true /* updateCache */); + verify(mWm.mTaskSnapshotController, never()).prepareTaskSnapshot(any(), any()); + mAtm.takeTaskSnapshot(disabledInRecentsTaskId, false /* updateCache */); + verify(mWm.mTaskSnapshotController).prepareTaskSnapshot(any(), any()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index a83caa4a4e95..dade3b91e0eb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -636,6 +636,7 @@ public class TransitionTests extends WindowTestsBase { transition.collect(app); controller.requestStartTransition(transition, null /* startTask */, remoteTransition, null /* displayChange */); + assertTrue(delegateProc.isRunningRemoteTransition()); testPlayer.startTransition(); app.onStartingWindowDrawn(); // The task appeared event should be deferred until transition ready. @@ -643,7 +644,6 @@ public class TransitionTests extends WindowTestsBase { testPlayer.onTransactionReady(app.getSyncTransaction()); assertTrue(task.taskAppearedReady()); assertTrue(playerProc.isRunningRemoteTransition()); - assertTrue(delegateProc.isRunningRemoteTransition()); assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread())); assertTrue(app.isVisible()); diff --git a/services/tests/wmtests/src/com/android/server/wm/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/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/OWNERS b/telephony/OWNERS index 3158ad8fc58e..287aa653ef9a 100644 --- a/telephony/OWNERS +++ b/telephony/OWNERS @@ -7,10 +7,12 @@ rgreenwalt@google.com tgunn@google.com huiwang@google.com jayachandranc@google.com -chinmayd@google.com amruthr@google.com sasindran@google.com # Requiring TL ownership for new carrier config keys. per-file CarrierConfigManager.java=set noparent per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com + +#Domain Selection is jointly owned, add additional owners for domain selection specific files +per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index baeff06a5836..c124079ca2e3 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8875,6 +8875,19 @@ public class CarrierConfigManager { KEY_PREFIX + "child_session_aes_ctr_key_size_int_array"; /** + * List of supported key sizes for AES Galois/Counter Mode (GCM) encryption mode + * of child session. + * Possible values are: + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = + KEY_PREFIX + "child_session_aes_gcm_key_size_int_array"; + + /** * List of supported encryption algorithms for child session. Possible values are * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC}, * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR} @@ -8883,6 +8896,16 @@ public class CarrierConfigManager { KEY_PREFIX + "supported_child_session_encryption_algorithms_int_array"; /** + * List of supported AEAD algorithms for child session. Possible values are + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_8}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_12}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_16} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY = + KEY_PREFIX + "supported_child_session_aead_algorithms_int_array"; + + /** * Time in seconds after which the IKE session is terminated if rekey procedure is not * successful. If not set or set to <= 0, default value is 3600 seconds. */ @@ -8919,6 +8942,19 @@ public class CarrierConfigManager { KEY_PREFIX + "ike_session_encryption_aes_ctr_key_size_int_array"; /** + * List of supported key sizes for AES Galois/Counter Mode (GCM) encryption mode + * of IKE session. + * Possible values - + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = + KEY_PREFIX + "ike_session_encryption_aes_gcm_key_size_int_array"; + + /** * List of supported encryption algorithms for IKE session. Possible values are * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC}, * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR} @@ -8927,6 +8963,16 @@ public class CarrierConfigManager { KEY_PREFIX + "supported_ike_session_encryption_algorithms_int_array"; /** + * List of supported AEAD algorithms for IKE session. Possible values are + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_8}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_12}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_16} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY = + KEY_PREFIX + "supported_ike_session_aead_algorithms_int_array"; + + /** * List of supported integrity algorithms for IKE session. Possible values are * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_NONE}, * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_HMAC_SHA1_96}, @@ -9156,9 +9202,13 @@ public class CarrierConfigManager { KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY, new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC}); defaults.putIntArray( + KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {}); + defaults.putIntArray( KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY, new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC}); defaults.putIntArray( + KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {}); + defaults.putIntArray( KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY, new int[] { SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96, @@ -9207,6 +9257,10 @@ public class CarrierConfigManager { SaProposal.KEY_LEN_AES_192, SaProposal.KEY_LEN_AES_256}); defaults.putIntArray( + KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {}); + defaults.putIntArray( + KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {}); + defaults.putIntArray( KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, new int[] {EPDG_ADDRESS_PLMN, EPDG_ADDRESS_STATIC}); defaults.putIntArray( diff --git a/telephony/java/android/telephony/NumberVerificationCallback.java b/telephony/java/android/telephony/NumberVerificationCallback.java index b00c57351589..71df1f2fa28c 100644 --- a/telephony/java/android/telephony/NumberVerificationCallback.java +++ b/telephony/java/android/telephony/NumberVerificationCallback.java @@ -20,6 +20,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A callback for number verification. After a request for number verification is received, * the system will call {@link #onCallReceived(String)} if a phone call was received from a number @@ -34,6 +37,7 @@ public interface NumberVerificationCallback { REASON_TOO_MANY_CALLS, REASON_CONCURRENT_REQUESTS, REASON_IN_ECBM, REASON_IN_EMERGENCY_CALL}, prefix = {"REASON_"}) + @Retention(RetentionPolicy.SOURCE) @interface NumberVerificationFailureReason {} /** diff --git a/telephony/java/android/telephony/PinResult.java b/telephony/java/android/telephony/PinResult.java index b8c1ffe58371..14713c7d4b12 100644 --- a/telephony/java/android/telephony/PinResult.java +++ b/telephony/java/android/telephony/PinResult.java @@ -25,6 +25,8 @@ import android.os.Parcelable; import com.android.internal.telephony.PhoneConstants; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -46,6 +48,7 @@ public final class PinResult implements Parcelable { PIN_RESULT_TYPE_FAILURE, PIN_RESULT_TYPE_ABORTED, }) + @Retention(RetentionPolicy.SOURCE) public @interface PinResultType {} /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 1c5761dcebe0..b96914e59c0e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -3400,6 +3400,7 @@ public class TelephonyManager { SIM_STATE_LOADED, SIM_STATE_PRESENT, }) + @Retention(RetentionPolicy.SOURCE) public @interface SimState {} /** @@ -10170,6 +10171,7 @@ public class TelephonyManager { CALL_COMPOSER_STATUS_ON, CALL_COMPOSER_STATUS_OFF, }) + @Retention(RetentionPolicy.SOURCE) public @interface CallComposerStatus {} /** @@ -13157,7 +13159,7 @@ public class TelephonyManager { CARRIER_RESTRICTION_STATUS_RESTRICTED, CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER }) - + @Retention(RetentionPolicy.SOURCE) public @interface CarrierRestrictionStatus { } diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 4b1a72695e50..3e8787281f85 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -951,8 +951,8 @@ public class ApnSetting implements Parcelable { * See 3GPP TS 23.501 section 5.6.13 * * @return True if the PDU session for this APN should always be on and false otherwise - * @hide */ + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public boolean isAlwaysOn() { return mAlwaysOn; } @@ -2282,9 +2282,9 @@ public class ApnSetting implements Parcelable { * See 3GPP TS 23.501 section 5.6.13 * * @param alwaysOn the always on status to set for this APN - * @hide */ - public Builder setAlwaysOn(boolean alwaysOn) { + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) + public @NonNull Builder setAlwaysOn(boolean alwaysOn) { this.mAlwaysOn = alwaysOn; return this; } diff --git a/telephony/java/android/telephony/data/ThrottleStatus.java b/telephony/java/android/telephony/data/ThrottleStatus.java index 0335c6868340..0dff6ff705ca 100644 --- a/telephony/java/android/telephony/data/ThrottleStatus.java +++ b/telephony/java/android/telephony/data/ThrottleStatus.java @@ -27,6 +27,8 @@ import android.os.SystemClock; import android.telephony.AccessNetworkConstants; import android.telephony.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -52,6 +54,7 @@ public final class ThrottleStatus implements Parcelable { ThrottleStatus.THROTTLE_TYPE_NONE, ThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME, }) + @Retention(RetentionPolicy.SOURCE) public @interface ThrottleType { } @@ -76,6 +79,7 @@ public final class ThrottleStatus implements Parcelable { ThrottleStatus.RETRY_TYPE_NEW_CONNECTION, ThrottleStatus.RETRY_TYPE_HANDOVER, }) + @Retention(RetentionPolicy.SOURCE) public @interface RetryType { } diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.java b/telephony/java/android/telephony/ims/MediaQualityStatus.java index 76394feaed66..e2df0d44fda8 100644 --- a/telephony/java/android/telephony/ims/MediaQualityStatus.java +++ b/telephony/java/android/telephony/ims/MediaQualityStatus.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.AccessNetworkConstants.TransportType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -49,6 +51,7 @@ public final class MediaQualityStatus implements Parcelable { MEDIA_SESSION_TYPE_AUDIO, MEDIA_SESSION_TYPE_VIDEO, }) + @Retention(RetentionPolicy.SOURCE) public @interface MediaSessionType {} /** diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java index f367e404a35b..39c9d8b94d3a 100644 --- a/telephony/java/android/telephony/ims/RcsClientConfiguration.java +++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java @@ -22,6 +22,8 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -35,6 +37,7 @@ public final class RcsClientConfiguration implements Parcelable { /**@hide*/ @StringDef(prefix = "RCS_PROFILE_", value = {RCS_PROFILE_1_0, RCS_PROFILE_2_3, RCS_PROFILE_2_4}) + @Retention(RetentionPolicy.SOURCE) public @interface StringRcsProfile {} /** diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 15a20cb91d04..4c53f8ab9bca 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -3147,4 +3147,16 @@ interface ITelephony { + "android.Manifest.permission.SATELLITE_COMMUNICATION)") void unregisterForSatelliteCapabilitiesChanged(int subId, in ISatelliteCapabilitiesCallback callback); + + /** + * This API can be used by only CTS to override the cached value for the device overlay config + * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether + * outgoing satellite datagrams should be sent to modem in demo mode. + * + * @param shouldSendToDemoMode Whether send datagram in demo mode should be sent to satellite + * modem or not. + * + * @return {@code true} if the operation is successful, {@code false} otherwise. + */ + boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode); } diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 159c6fd9ef58..c638873873dc 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -2514,6 +2514,28 @@ int LinkCommand::Action(const std::vector<std::string>& args) { } } + // Parse the feature flag values. An argument that starts with '@' points to a file to read flag + // values from. + std::vector<std::string> all_feature_flags_args; + for (const std::string& arg : feature_flags_args_) { + if (util::StartsWith(arg, "@")) { + const std::string path = arg.substr(1, arg.size() - 1); + std::string error; + if (!file::AppendArgsFromFile(path, &all_feature_flags_args, &error)) { + context.GetDiagnostics()->Error(android::DiagMessage(path) << error); + return 1; + } + } else { + all_feature_flags_args.push_back(arg); + } + } + + for (const std::string& arg : all_feature_flags_args) { + if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) { + return 1; + } + } + if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path_) { if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path_.value(), &options_.stable_id_map)) { diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index a08f385b2270..26713fd92264 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -17,11 +17,17 @@ #ifndef AAPT2_LINK_H #define AAPT2_LINK_H +#include <optional> #include <regex> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> #include "Command.h" #include "Resource.h" #include "androidfw/IDiagnostics.h" +#include "cmd/Util.h" #include "format/binary/TableFlattener.h" #include "format/proto/ProtoSerialize.h" #include "link/ManifestFixer.h" @@ -72,6 +78,7 @@ struct LinkOptions { bool use_sparse_encoding = false; std::unordered_set<std::string> extensions_to_not_compress; std::optional<std::regex> regex_to_not_compress; + FeatureFlagValues feature_flag_values; // Static lib options. bool no_static_lib_packages = false; diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index a92f24b82547..678d84628015 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -113,6 +113,56 @@ std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std return std::move(filter); } +bool ParseFeatureFlagsParameter(StringPiece arg, android::IDiagnostics* diag, + FeatureFlagValues* out_feature_flag_values) { + if (arg.empty()) { + return true; + } + + for (StringPiece flag_and_value : util::Tokenize(arg, ',')) { + std::vector<std::string> parts = util::Split(flag_and_value, '='); + if (parts.empty()) { + continue; + } + + if (parts.size() > 2) { + diag->Error(android::DiagMessage() + << "Invalid feature flag and optional value '" << flag_and_value + << "'. Must be in the format 'flag_name[=true|false]"); + return false; + } + + StringPiece flag_name = util::TrimWhitespace(parts[0]); + if (flag_name.empty()) { + diag->Error(android::DiagMessage() << "No name given for one or more flags in: " << arg); + return false; + } + + std::optional<bool> flag_value = {}; + if (parts.size() == 2) { + StringPiece str_flag_value = util::TrimWhitespace(parts[1]); + if (!str_flag_value.empty()) { + flag_value = ResourceUtils::ParseBool(parts[1]); + if (!flag_value.has_value()) { + diag->Error(android::DiagMessage() << "Invalid value for feature flag '" << flag_and_value + << "'. Value must be 'true' or 'false'"); + return false; + } + } + } + + if (auto [it, inserted] = + out_feature_flag_values->try_emplace(std::string(flag_name), flag_value); + !inserted) { + // We are allowing the same flag to appear multiple times, last value wins. + diag->Note(android::DiagMessage() + << "Value for feature flag '" << flag_name << "' was given more than once"); + it->second = flag_value; + } + } + return true; +} + // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the minSdk. Otherwise the resources that have had // their SDK version stripped due to minSdk won't ever match. diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h index 712c07b71695..9ece5dd4d720 100644 --- a/tools/aapt2/cmd/Util.h +++ b/tools/aapt2/cmd/Util.h @@ -17,8 +17,13 @@ #ifndef AAPT_SPLIT_UTIL_H #define AAPT_SPLIT_UTIL_H +#include <functional> +#include <map> +#include <memory> +#include <optional> #include <regex> #include <set> +#include <string> #include <unordered_set> #include "AppInfo.h" @@ -32,6 +37,8 @@ namespace aapt { +using FeatureFlagValues = std::map<std::string, std::optional<bool>, std::less<>>; + // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc). // Returns Nothing and logs a human friendly error message if the string was not legal. std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg, @@ -48,6 +55,13 @@ bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag, std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args, android::IDiagnostics* diag); +// Parses a feature flags parameter, which can contain one or more pairs of flag names and optional +// values, and fills in `out_feature_flag_values` with the parsed values. The pairs in the argument +// are separated by ',' and the name is separated from the value by '=' if there is a value given. +// Example arg: "flag1=true,flag2=false,flag3=,flag4" where flag3 and flag4 have no given value. +bool ParseFeatureFlagsParameter(android::StringPiece arg, android::IDiagnostics* diag, + FeatureFlagValues* out_feature_flag_values); + // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the min_sdk. Otherwise the resources that have had // their SDK version stripped due to min_sdk won't ever match. diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp index 139bfbcd0f41..723d87ed0af3 100644 --- a/tools/aapt2/cmd/Util_test.cpp +++ b/tools/aapt2/cmd/Util_test.cpp @@ -25,6 +25,7 @@ #include "util/Files.h" using ::android::ConfigDescription; +using testing::Pair; using testing::UnorderedElementsAre; namespace aapt { @@ -354,6 +355,51 @@ TEST (UtilTest, ParseSplitParameters) { EXPECT_CONFIG_EQ(constraints, expected_configuration); } +TEST(UtilTest, ParseFeatureFlagsParameter_Empty) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_TRUE(ParseFeatureFlagsParameter("", diagnostics, &feature_flag_values)); + EXPECT_TRUE(feature_flag_values.empty()); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_TooManyParts) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_FALSE(ParseFeatureFlagsParameter("foo=bar=baz", diagnostics, &feature_flag_values)); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_NoNameGiven) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,=false", diagnostics, &feature_flag_values)); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_InvalidValue) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,bar=42", diagnostics, &feature_flag_values)); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_DuplicateFlag) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_TRUE( + ParseFeatureFlagsParameter("foo=true,bar=true,foo=false", diagnostics, &feature_flag_values)); + EXPECT_THAT(feature_flag_values, UnorderedElementsAre(Pair("foo", std::optional<bool>(false)), + Pair("bar", std::optional<bool>(true)))); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_Valid) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_TRUE(ParseFeatureFlagsParameter("foo= true, bar =FALSE,baz=, quux", diagnostics, + &feature_flag_values)); + EXPECT_THAT(feature_flag_values, + UnorderedElementsAre(Pair("foo", std::optional<bool>(true)), + Pair("bar", std::optional<bool>(false)), + Pair("baz", std::nullopt), Pair("quux", std::nullopt))); +} + TEST (UtilTest, AdjustSplitConstraintsForMinSdk) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); diff --git a/tools/lint/README.md b/tools/lint/README.md index b235ad60c799..ff8e44229189 100644 --- a/tools/lint/README.md +++ b/tools/lint/README.md @@ -103,10 +103,15 @@ out/soong/.intermediates/frameworks/base/services/autofill/services.autofill/and As noted above, this baseline file contains warnings too, which might be undesirable. For example, CI tools might surface these warnings in code reviews. In order to create this file without -warnings, we need to pass another flag to lint: `--nowarn`. The easiest way to do this is to -locally change the soong code in -[lint.go](http://cs/aosp-master/build/soong/java/lint.go;l=451;rcl=2e778d5bc4a8d1d77b4f4a3029a4a254ad57db75) -adding `cmd.Flag("--nowarn")` and running lint again. +warnings, we need to pass another flag to lint: `--nowarn`. One option is to add the flag to your +Android.bp file and then run lint again: + +``` + lint: { + extra_check_modules: ["AndroidFrameworkLintChecker"], + flags: ["--nowarn"], + } +``` # Documentation diff --git a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt index d41fee3fc0dc..24d203fd1116 100644 --- a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt +++ b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt @@ -24,33 +24,31 @@ import com.intellij.psi.PsiReferenceList import org.jetbrains.uast.UMethod /** - * Given a UMethod, determine if this method is - * the entrypoint to an interface generated by AIDL, - * returning the interface name if so, otherwise returning null + * Given a UMethod, determine if this method is the entrypoint to an interface + * generated by AIDL, returning the interface name if so, otherwise returning + * null */ fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? { - if (!isContainedInSubclassOfStub(context, node)) return null - for (superMethod in node.findSuperMethods()) { - for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements - ?: continue) { - if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) { - return superMethod.containingClass?.name - } - } - } - return null + val containingStub = containingStub(context, node) ?: return null + val superMethod = node.findSuperMethods(containingStub) + if (superMethod.isEmpty()) return null + return containingStub.containingClass?.name } -fun isContainedInSubclassOfStub(context: JavaContext, node: UMethod?): Boolean { +/* Returns the containing Stub class if any. This is not sufficient to infer + * that the method itself extends an AIDL generated method. See + * getContainingAidlInterface for that purpose. + */ +fun containingStub(context: JavaContext, node: UMethod?): PsiClass? { var superClass = node?.containingClass?.superClass while (superClass != null) { - if (isStub(context, superClass)) return true + if (isStub(context, superClass)) return superClass superClass = superClass.superClass } - return false + return null } -fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean { +private fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean { if (psiClass == null) return false if (psiClass.name != "Stub") return false if (!context.evaluator.isStatic(psiClass)) return false diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt index 935badecf8d5..624a1987638e 100644 --- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -20,6 +20,7 @@ import com.android.tools.lint.client.api.IssueRegistry import com.android.tools.lint.client.api.Vendor import com.android.tools.lint.detector.api.CURRENT_API import com.google.android.lint.parcel.SaferParcelChecker +import com.google.android.lint.aidl.PermissionAnnotationDetector import com.google.auto.service.AutoService @AutoService(IssueRegistry::class) @@ -37,6 +38,7 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, // TODO: Currently crashes due to OOM issue // PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS, + PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE, PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD, ) diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt new file mode 100644 index 000000000000..6b50cfd9e5ab --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UMethod + +/** + * Ensures all AIDL-generated methods are annotated. + * + * This detector is run on system_server to validate that any method that may + * be exposed via an AIDL interface is permission-annotated. That is, it must + * have one of the following annotation: + * - @EnforcePermission + * - @RequiresNoPermission + * - @PermissionManuallyEnforced + */ +class PermissionAnnotationDetector : AidlImplementationDetector() { + + override fun visitAidlMethod( + context: JavaContext, + node: UMethod, + interfaceName: String, + body: UBlockExpression + ) { + if (context.evaluator.isAbstract(node)) return + + if (AIDL_PERMISSION_ANNOTATIONS.any { node.hasAnnotation(it) }) return + + context.report( + ISSUE_MISSING_PERMISSION_ANNOTATION, + node, + context.getLocation(node), + "The method ${node.name} is not permission-annotated." + ) + } + + companion object { + + private val EXPLANATION_MISSING_PERMISSION_ANNOTATION = """ + Interfaces that are exposed by system_server are required to have an annotation which + denotes the type of permission enforced. There are 3 possible options: + - @EnforcePermission + - @RequiresNoPermission + - @PermissionManuallyEnforced + See the documentation of each annotation for further details. + + The annotation on the Java implementation must be the same that the AIDL interface + definition. This is verified by a lint in the build system. + """.trimIndent() + + @JvmField + val ISSUE_MISSING_PERMISSION_ANNOTATION = Issue.create( + id = "MissingPermissionAnnotation", + briefDescription = "No permission annotation on exposed AIDL interface.", + explanation = EXPLANATION_MISSING_PERMISSION_ANNOTATION, + category = Category.CORRECTNESS, + priority = 5, + severity = Severity.ERROR, + implementation = Implementation( + PermissionAnnotationDetector::class.java, + Scope.JAVA_FILE_SCOPE + ), + enabledByDefault = false + ) + } +} diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt new file mode 100644 index 000000000000..bce848a2e3a7 --- /dev/null +++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class PermissionAnnotationDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = PermissionAnnotationDetector() + + override fun getIssues(): List<Issue> = listOf( + PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + /** No issue scenario */ + + fun testDoesNotDetectIssuesInCorrectScenario() { + lint().files( + java( + """ + public class Foo extends IFoo.Stub { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void testMethod() { } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMissingAnnotation() { + lint().files( + java( + """ + public class Bar extends IBar.Stub { + public void testMethod() { } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Bar.java:2: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation] + public void testMethod() { } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + ) + } + + fun testNoIssueWhenExtendingWithAnotherSubclass() { + lint().files( + java( + """ + public class Foo extends IFoo.Stub { + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod() { } + // not an AIDL method, just another method + public void someRandomMethod() { } + } + """).indented(), + java( + """ + public class Baz extends Bar { + @Override + public void someRandomMethod() { } + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + /* Stubs */ + + // A service with permission annotation on the method. + private val interfaceIFoo: TestFile = java( + """ + public interface IFoo extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IFoo { + } + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod(); + @Override + @android.annotation.RequiresNoPermission + public void testMethodNoPermission(); + @Override + @android.annotation.PermissionManuallyEnforced + public void testMethodManual(); + } + """ + ).indented() + + // A service with no permission annotation. + private val interfaceIBar: TestFile = java( + """ + public interface IBar extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IBar { + } + public void testMethod(); + } + """ + ).indented() + + private val stubs = arrayOf(interfaceIFoo, interfaceIBar) +} diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt index 83b8f163abee..4455a9cda3a8 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt @@ -168,7 +168,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { annotationInfo.origin == AnnotationOrigin.METHOD) { /* Ignore implementations that are not a sub-class of Stub (i.e., Proxy). */ val uMethod = element as? UMethod ?: return - if (!isContainedInSubclassOfStub(context, uMethod)) { + if (getContainingAidlInterface(context, uMethod) == null) { return } val overridingMethod = element.sourcePsi as PsiMethod @@ -184,7 +184,8 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { if (context.evaluator.isAbstract(node)) return if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return - if (!isContainedInSubclassOfStub(context, node)) { + val stubClass = containingStub(context, node) + if (stubClass == null) { context.report( ISSUE_MISUSING_ENFORCE_PERMISSION, node, @@ -196,7 +197,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { /* Check that we are connected to the super class */ val overridingMethod = node as PsiMethod - val parents = overridingMethod.findSuperMethods() + val parents = overridingMethod.findSuperMethods(stubClass) if (parents.isEmpty()) { context.report( ISSUE_MISUSING_ENFORCE_PERMISSION, diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt index d8afcb977594..2afca05f8130 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt @@ -176,6 +176,29 @@ class EnforcePermissionDetectorTest : LintDetectorTest() { """.addLineContinuation()) } + fun testDetectNoIssuesAnnotationOnNonStubMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass43 extends IFooMethod.Stub { + public void aRegularMethodNotPartOfStub() { + } + } + """).indented(), java( + """ + package test.pkg; + public class TestClass44 extends TestClass43 { + @Override + public void aRegularMethodNotPartOfStub() { + } + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + fun testDetectIssuesEmptyAnnotationOnMethod() { lint().files(java( """ diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java index ebda6f1c5826..4f5e0e48c793 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java @@ -70,14 +70,8 @@ public abstract class SharedConnectivityService extends Service { private List<HotspotNetwork> mHotspotNetworks = Collections.emptyList(); private List<KnownNetwork> mKnownNetworks = Collections.emptyList(); private SharedConnectivitySettingsState mSettingsState = null; - private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus = - new HotspotNetworkConnectionStatus.Builder() - .setStatus(HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN) - .setExtras(Bundle.EMPTY).build(); - private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus = - new KnownNetworkConnectionStatus.Builder() - .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN) - .setExtras(Bundle.EMPTY).build(); + private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus = null; + private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus = null; // Used for testing private CountDownLatch mCountDownLatch; diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java index c6f67987746a..48ac82dc54a8 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java @@ -394,6 +394,26 @@ public class SharedConnectivityServiceTest { verify(mCallback, never()).onKnownNetworkConnectionStatusChanged(any()); } + @Test + public void getHotspotNetworkConnectionStatus_withoutUpdate_returnsNull() + throws RemoteException { + SharedConnectivityService service = createService(); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + assertThat(binder.getHotspotNetworkConnectionStatus()).isNull(); + } + + @Test + public void getKnownNetworkConnectionStatus_withoutUpdate_returnsNull() + throws RemoteException { + SharedConnectivityService service = createService(); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + assertThat(binder.getKnownNetworkConnectionStatus()).isNull(); + } + private FakeSharedConnectivityService createService() { FakeSharedConnectivityService service = new FakeSharedConnectivityService(); service.attachBaseContext(mContext); |