diff options
580 files changed, 12959 insertions, 6981 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index ce3e985e22d5..b54022bf47f2 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -64,6 +64,7 @@ aconfig_srcjars = [ ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}", ":android.tracing.flags-aconfig-java{.generated_srcjars}", ":android.appwidget.flags-aconfig-java{.generated_srcjars}", + ":android.webkit.flags-aconfig-java{.generated_srcjars}", ] filegroup { @@ -762,3 +763,19 @@ java_aconfig_library { aconfig_declarations: "android.appwidget.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// WebView +aconfig_declarations { + name: "android.webkit.flags-aconfig", + package: "android.webkit", + srcs: [ + "core/java/android/webkit/*.aconfig", + "services/core/java/com/android/server/webkit/*.aconfig", + ], +} + +java_aconfig_library { + name: "android.webkit.flags-aconfig-java", + aconfig_declarations: "android.webkit.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Ravenwood.bp b/Ravenwood.bp index 03a23ba15273..ca73378c8e81 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -32,7 +32,6 @@ java_genrule { cmd: "$(location hoststubgen) " + "@$(location ravenwood/ravenwood-standard-options.txt) " + - "--out-stub-jar $(location ravenwood_stub.jar) " + "--out-impl-jar $(location ravenwood.jar) " + "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " + @@ -49,7 +48,6 @@ java_genrule { ], out: [ "ravenwood.jar", - "ravenwood_stub.jar", // It's not used. TODO: Update hoststubgen to make it optional. // Following files are created just as FYI. "hoststubgen_keep_all.txt", diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp index 9366ff2d81a9..e1b3241e051e 100644 --- a/apct-tests/perftests/core/Android.bp +++ b/apct-tests/perftests/core/Android.bp @@ -66,6 +66,7 @@ android_test { errorprone: { javacflags: [ "-Xep:ReturnValueIgnored:WARN", + "-Xep:UnnecessaryStringBuilder:OFF", ], }, } diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java index c42c7ca25133..2ace6028456e 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java @@ -128,6 +128,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration(); final InsetsState mOutInsetsState = new InsetsState(); final InsetsSourceControl.Array mOutControls = new InsetsSourceControl.Array(); + final Bundle mOutBundle = new Bundle(); final IWindow mWindow; final View mView; final WindowManager.LayoutParams mParams; @@ -136,7 +137,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase final SurfaceControl mOutSurfaceControl; final IntSupplier mViewVisibility; - + int mRelayoutSeq; int mFlags; RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) { @@ -152,10 +153,11 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase void runBenchmark(BenchmarkState state) throws RemoteException { final IWindowSession session = WindowManagerGlobal.getWindowSession(); while (state.keepRunning()) { + mRelayoutSeq++; session.relayout(mWindow, mParams, mWidth, mHeight, - mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */, + mViewVisibility.getAsInt(), mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */, mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, - mOutControls, new Bundle()); + mOutControls, mOutBundle); } } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index b828f39a120c..13bea6bd1dd1 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -598,7 +598,6 @@ public final class JobStatus { long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs, int internalFlags, int dynamicConstraints) { - this.job = job; this.callingUid = callingUid; this.standbyBucket = standbyBucket; mNamespace = namespace; @@ -626,6 +625,22 @@ public final class JobStatus { this.sourceTag = tag; } + // This needs to be done before setting the field variable. + if (job.getRequiredNetwork() != null) { + // Later, when we check if a given network satisfies the required + // network, we need to know the UID that is requesting it, so push + // the source UID into place. + final JobInfo.Builder builder = new JobInfo.Builder(job); + builder.setRequiredNetwork(new NetworkRequest.Builder(job.getRequiredNetwork()) + .setUids(Collections.singleton(new Range<>(this.sourceUid, this.sourceUid))) + .build()); + // Don't perform validation checks at this point since we've already passed the + // initial validation check. + job = builder.build(false, false); + } + + this.job = job; + final String bnNamespace = namespace == null ? "" : "@" + namespace + "@"; this.batteryName = this.sourceTag != null ? bnNamespace + this.sourceTag + ":" + job.getService().getPackageName() @@ -708,21 +723,6 @@ public final class JobStatus { updateNetworkBytesLocked(); - if (job.getRequiredNetwork() != null) { - // Later, when we check if a given network satisfies the required - // network, we need to know the UID that is requesting it, so push - // our source UID into place. - final JobInfo.Builder builder = new JobInfo.Builder(job); - final NetworkRequest.Builder requestBuilder = - new NetworkRequest.Builder(job.getRequiredNetwork()); - requestBuilder.setUids( - Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid))); - builder.setRequiredNetwork(requestBuilder.build()); - // Don't perform validation checks at this point since we've already passed the - // initial validation check. - job = builder.build(false, false); - } - updateMediaBackupExemptionStatus(); } diff --git a/api/Android.bp b/api/Android.bp index d6c14fbdfae3..2b1cfcb82d04 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -404,3 +404,49 @@ build = [ "ApiDocs.bp", "StubLibraries.bp", ] + +genrule_defaults { + name: "flag-api-mapping-generation-defaults", + cmd: "$(location extract-flagged-apis) $(in) $(out)", + tools: ["extract-flagged-apis"], +} + +genrule { + name: "flag-api-mapping-PublicApi", + defaults: ["flag-api-mapping-generation-defaults"], + srcs: [":frameworks-base-api-current.txt"], + out: ["flag_api_map.textproto"], + dist: { + targets: ["droid"], + }, +} + +genrule { + name: "flag-api-mapping-SystemApi", + defaults: ["flag-api-mapping-generation-defaults"], + srcs: [":frameworks-base-api-system-current.txt"], + out: ["system_flag_api_map.textproto"], + dist: { + targets: ["droid"], + }, +} + +genrule { + name: "flag-api-mapping-ModuleLibApi", + defaults: ["flag-api-mapping-generation-defaults"], + srcs: [":frameworks-base-api-module-lib-current.txt"], + out: ["module_lib_flag_api_map.textproto"], + dist: { + targets: ["droid"], + }, +} + +genrule { + name: "flag-api-mapping-SystemServerApi", + defaults: ["flag-api-mapping-generation-defaults"], + srcs: [":frameworks-base-api-system-server-current.txt"], + out: ["system_server_flag_api_map.textproto"], + dist: { + targets: ["droid"], + }, +} diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt index 948e64f22f20..9ffb70496c59 100644 --- a/api/coverage/tools/ExtractFlaggedApis.kt +++ b/api/coverage/tools/ExtractFlaggedApis.kt @@ -25,7 +25,7 @@ fun main(args: Array<String>) { var cb = ApiFile.parseApi(listOf(File(args[0]))) val flagToApi = mutableMapOf<String, MutableList<String>>() cb.getPackages() - .allTopLevelClasses() + .allClasses() .filter { it.methods().size > 0 } .forEach { for (method in it.methods()) { diff --git a/cmds/gpu_counter_producer/Android.bp b/cmds/gpu_counter_producer/Android.bp index 2232345ce4fa..d645d066befd 100644 --- a/cmds/gpu_counter_producer/Android.bp +++ b/cmds/gpu_counter_producer/Android.bp @@ -19,6 +19,4 @@ cc_binary { "-Wunused", "-Wunreachable-code", ], - - soc_specific: true, } diff --git a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java new file mode 100644 index 000000000000..b89e2cdbd905 --- /dev/null +++ b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java @@ -0,0 +1,380 @@ +/* + * 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.annotation.Nullable; +import android.util.SparseArray; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +import src.com.android.commands.uinput.InputAbsInfo; + +/** + * Parser for the <a href="https://gitlab.freedesktop.org/libevdev/evemu">FreeDesktop evemu</a> + * event recording format. + */ +public class EvemuParser implements EventParser { + private static final String TAG = "UinputEvemuParser"; + + /** + * The device ID to use for all events. Since evemu files only support single-device + * recordings, this will always be the same. + */ + private static final int DEVICE_ID = 1; + private static final int REGISTRATION_DELAY_MILLIS = 500; + + private static class CommentAwareReader { + private final BufferedReader mReader; + private String mNextLine; + + CommentAwareReader(BufferedReader in) throws IOException { + mReader = in; + mNextLine = findNextLine(); + } + + private @Nullable String findNextLine() throws IOException { + String line = ""; + while (line != null && line.length() == 0) { + String unstrippedLine = mReader.readLine(); + if (unstrippedLine == null) { + // End of file. + return null; + } + line = stripComments(unstrippedLine); + } + return line; + } + + private static String stripComments(String line) { + int index = line.indexOf('#'); + // 'N:' lines (which contain the name of the input device) do not support trailing + // comments, to support recording device names that contain #s. + if (index < 0 || line.startsWith("N: ")) { + return line; + } else { + return line.substring(0, index).strip(); + } + } + + /** + * Returns the next line of the file that isn't blank when stripped of comments, or + * {@code null} if the end of the file is reached. However, it does not advance to the + * next line of the file. + */ + public @Nullable String peekLine() { + return mNextLine; + } + + /** Moves to the next line of the file. */ + public void advance() throws IOException { + mNextLine = findNextLine(); + } + + public boolean isAtEndOfFile() { + return mNextLine == null; + } + } + + private final CommentAwareReader mReader; + /** + * The timestamp of the last event returned, of the head of {@link #mQueuedEvents} if there is + * one, or -1 if no events have been returned yet. + */ + private long mLastEventTimeMicros = -1; + private final Queue<Event> mQueuedEvents = new ArrayDeque<>(2); + + public EvemuParser(Reader in) throws IOException { + mReader = new CommentAwareReader(new BufferedReader(in)); + mQueuedEvents.add(parseRegistrationEvent()); + + // The kernel takes a little time to set up an evdev device after the initial + // registration. Any events that we try to inject during this period would be silently + // dropped, so we delay for a short period after registration and before injecting any + // events. + final Event.Builder delayEb = new Event.Builder(); + delayEb.setId(DEVICE_ID); + delayEb.setCommand(Event.Command.DELAY); + delayEb.setDurationMillis(REGISTRATION_DELAY_MILLIS); + mQueuedEvents.add(delayEb.build()); + } + + /** + * Returns the next event in the evemu recording. + */ + public Event getNextEvent() throws IOException { + if (!mQueuedEvents.isEmpty()) { + return mQueuedEvents.remove(); + } + + if (mReader.isAtEndOfFile()) { + return null; + } + + final String[] parts = expectLineWithParts("E", 4); + final String[] timeParts = parts[0].split("\\."); + if (timeParts.length != 2) { + throw new RuntimeException("Invalid timestamp (does not contain a '.')"); + } + // TODO(b/310958309): use timeMicros to set the timestamp on the event being sent. + final long timeMicros = + Long.parseLong(timeParts[0]) * 1_000_000 + Integer.parseInt(timeParts[1]); + final Event.Builder eb = new Event.Builder(); + eb.setId(DEVICE_ID); + eb.setCommand(Event.Command.INJECT); + final int eventType = Integer.parseInt(parts[1], 16); + final int eventCode = Integer.parseInt(parts[2], 16); + final int value = Integer.parseInt(parts[3]); + eb.setInjections(new int[] {eventType, eventCode, value}); + + if (mLastEventTimeMicros == -1) { + // This is the first event being injected, so send it straight away. + mLastEventTimeMicros = timeMicros; + return eb.build(); + } else { + final long delayMicros = timeMicros - mLastEventTimeMicros; + // The shortest delay supported by Handler.sendMessageAtTime (used for timings by the + // Device class) is 1ms, so ignore time differences smaller than that. + if (delayMicros < 1000) { + mLastEventTimeMicros = timeMicros; + return eb.build(); + } else { + // Send a delay now, and queue the actual event for the next call. + mQueuedEvents.add(eb.build()); + mLastEventTimeMicros = timeMicros; + final Event.Builder delayEb = new Event.Builder(); + delayEb.setId(DEVICE_ID); + delayEb.setCommand(Event.Command.DELAY); + delayEb.setDurationMillis((int) (delayMicros / 1000)); + return delayEb.build(); + } + } + } + + private Event parseRegistrationEvent() throws IOException { + // The registration details at the start of a recording are specified by a set of lines + // that have to be in this order: N, I, P, B, A, L, S. Recordings must have exactly one N + // (name) and I (IDs) line. The remaining lines are optional, and there may be multiple + // of those lines. + + final Event.Builder eb = new Event.Builder(); + eb.setId(DEVICE_ID); + eb.setCommand(Event.Command.REGISTER); + eb.setName(expectLine("N")); + + final String[] idStrings = expectLineWithParts("I", 4); + eb.setBusId(Integer.parseInt(idStrings[0], 16)); + eb.setVid(Integer.parseInt(idStrings[1], 16)); + eb.setPid(Integer.parseInt(idStrings[2], 16)); + // TODO(b/302297266): support setting the version ID, and set it to idStrings[3]. + + final SparseArray<int[]> config = new SparseArray<>(); + config.append(Event.UinputControlCode.UI_SET_PROPBIT.getValue(), parseProperties()); + + parseAxisBitmaps(config); + + eb.setConfiguration(config); + if (config.contains(Event.UinputControlCode.UI_SET_FFBIT.getValue())) { + // If the device specifies any force feedback effects, the kernel will require the + // ff_effects_max value to be set. + eb.setFfEffectsMax(config.get(Event.UinputControlCode.UI_SET_FFBIT.getValue()).length); + } + + eb.setAbsInfo(parseAbsInfos()); + + // L: and S: lines allow the initial states of the device's LEDs and switches to be + // recorded. However, the FreeDesktop implementation doesn't support actually setting these + // states at the start of playback (apparently due to concerns over race conditions), and we + // have no need for this feature either, so for now just skip over them. + skipUnsupportedLines("L"); + skipUnsupportedLines("S"); + + return eb.build(); + } + + private int[] parseProperties() throws IOException { + final List<String> propBitmapParts = new ArrayList<>(); + String line = acceptLine("P"); + while (line != null) { + propBitmapParts.addAll(List.of(line.strip().split(" "))); + line = acceptLine("P"); + } + return hexStringBitmapToEventCodes(propBitmapParts); + } + + private void parseAxisBitmaps(SparseArray<int[]> config) throws IOException { + final Map<Integer, List<String>> axisBitmapParts = new HashMap<>(); + String line = acceptLine("B"); + while (line != null) { + final String[] parts = line.strip().split(" "); + if (parts.length < 2) { + throw new RuntimeException( + "Expected event type and at least one bitmap byte on 'B:' line; only found " + + parts.length + " elements"); + } + final int eventType = Integer.parseInt(parts[0], 16); + // EV_SYN cannot be configured through uinput, so skip it. + if (eventType != Event.EV_SYN) { + if (!axisBitmapParts.containsKey(eventType)) { + axisBitmapParts.put(eventType, new ArrayList<>()); + } + for (int i = 1; i < parts.length; i++) { + axisBitmapParts.get(eventType).add(parts[i]); + } + } + line = acceptLine("B"); + } + final List<Integer> eventTypesToSet = new ArrayList<>(); + for (var entry : axisBitmapParts.entrySet()) { + if (entry.getValue().size() == 0) { + continue; + } + final Event.UinputControlCode controlCode = + Event.UinputControlCode.forEventType(entry.getKey()); + final int[] eventCodes = hexStringBitmapToEventCodes(entry.getValue()); + if (controlCode != null && eventCodes.length > 0) { + config.append(controlCode.getValue(), eventCodes); + eventTypesToSet.add(entry.getKey()); + } + } + config.append( + Event.UinputControlCode.UI_SET_EVBIT.getValue(), unboxIntList(eventTypesToSet)); + } + + private SparseArray<InputAbsInfo> parseAbsInfos() throws IOException { + final SparseArray<InputAbsInfo> absInfos = new SparseArray<>(); + String line = acceptLine("A"); + while (line != null) { + final String[] parts = line.strip().split(" "); + if (parts.length < 5 || parts.length > 6) { + throw new RuntimeException( + "'A:' lines should have the format 'A: <index (hex)> <min> <max> <fuzz> " + + "<flat> [<resolution>]'; expected 5 or 6 numbers but found " + + parts.length); + } + final int axisCode = Integer.parseInt(parts[0], 16); + final InputAbsInfo info = new InputAbsInfo(); + info.minimum = Integer.parseInt(parts[1]); + info.maximum = Integer.parseInt(parts[2]); + info.fuzz = Integer.parseInt(parts[3]); + info.flat = Integer.parseInt(parts[4]); + info.resolution = parts.length > 5 ? Integer.parseInt(parts[5]) : 0; + absInfos.append(axisCode, info); + line = acceptLine("A"); + } + return absInfos; + } + + private void skipUnsupportedLines(String type) throws IOException { + if (acceptLine(type) != null) { + while (acceptLine(type) != null) { + // Skip the line. + } + } + } + + /** + * Returns the contents of the next line in the file if it has the given type, or raises an + * error if it does not. + * + * @param type the type of the line to expect, represented by the letter before the ':'. + * @return the part of the line after the ": ". + */ + private String expectLine(String type) throws IOException { + final String line = acceptLine(type); + if (line == null) { + throw new RuntimeException("Expected line of type '" + type + "'"); + } else { + return line; + } + } + + /** + * Peeks at the next line in the file to see if it has the given type, and if so, returns its + * contents and advances the reader. + * + * @param type the type of the line to accept, represented by the letter before the ':'. + * @return the part of the line after the ": ", if the type matches; otherwise {@code null}. + */ + private @Nullable String acceptLine(String type) throws IOException { + final String line = mReader.peekLine(); + if (line == null) { + return null; + } + final String[] lineParts = line.split(": ", 2); + if (lineParts.length < 2) { + // TODO(b/302297266): make a proper exception class for syntax errors, including line + // numbers, etc.. (We can use LineNumberReader to track them.) + throw new RuntimeException("Line without ': '"); + } + if (lineParts[0].equals(type)) { + mReader.advance(); + return lineParts[1]; + } else { + return null; + } + } + + /** + * Like {@link #expectLine(String)}, but also checks that the contents of the line is formed of + * {@code numParts} space-separated parts. + * + * @param type the type of the line to expect, represented by the letter before the ':'. + * @param numParts the number of parts to expect. + * @return the part of the line after the ": ", split into {@code numParts} sections. + */ + private String[] expectLineWithParts(String type, int numParts) throws IOException { + final String[] parts = expectLine(type).strip().split(" "); + if (parts.length != numParts) { + throw new RuntimeException("Expected a '" + type + "' line with " + numParts + + " parts, found one with " + parts.length); + } + return parts; + } + + private static int[] hexStringBitmapToEventCodes(List<String> strs) { + final List<Integer> codes = new ArrayList<>(); + for (int iByte = 0; iByte < strs.size(); iByte++) { + int b = Integer.parseInt(strs.get(iByte), 16); + if (b < 0x0 || b > 0xff) { + throw new RuntimeException("Bitmap part '" + strs.get(iByte) + + "' invalid; parts must be between 00 and ff."); + } + for (int iBit = 0; iBit < 8; iBit++) { + if ((b & 1) != 0) { + codes.add(iByte * 8 + iBit); + } + b >>= 1; + } + } + return unboxIntList(codes); + } + + private static int[] unboxIntList(List<Integer> list) { + final int[] array = new int[list.size()]; + Arrays.setAll(array, list::get); + return array; + } +} diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java index 4498bc2a09d6..5ec40e5d04e3 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Event.java +++ b/cmds/uinput/src/com/android/commands/uinput/Event.java @@ -16,6 +16,7 @@ package com.android.commands.uinput; +import android.annotation.Nullable; import android.util.SparseArray; import java.util.Arrays; @@ -39,6 +40,7 @@ public class Event { // Constants representing evdev event types, from include/uapi/linux/input-event-codes.h in the // kernel. + public static final int EV_SYN = 0x00; public static final int EV_KEY = 0x01; public static final int EV_REL = 0x02; public static final int EV_ABS = 0x03; @@ -69,19 +71,23 @@ public class Event { public int getValue() { return mValue; } - } - - // These constants come from "include/uapi/linux/input.h" in the kernel - enum Bus { - USB(0x03), BLUETOOTH(0x05); - private final int mValue; - - Bus(int value) { - mValue = value; - } - int getValue() { - return mValue; + /** + * Returns the control code for the given evdev event type, or {@code null} if there is no + * control code for that type. + */ + public static @Nullable UinputControlCode forEventType(int eventType) { + return switch (eventType) { + case EV_KEY -> UI_SET_KEYBIT; + case EV_REL -> UI_SET_RELBIT; + case EV_ABS -> UI_SET_ABSBIT; + case EV_MSC -> UI_SET_MSCBIT; + case EV_SW -> UI_SET_SWBIT; + case EV_LED -> UI_SET_LEDBIT; + case EV_SND -> UI_SET_SNDBIT; + case EV_FF -> UI_SET_FFBIT; + default -> null; + }; } } @@ -90,7 +96,7 @@ public class Event { private String mName; private int mVid; private int mPid; - private Bus mBus; + private int mBusId; private int[] mInjections; private SparseArray<int[]> mConfiguration; private int mDurationMillis; @@ -120,7 +126,7 @@ public class Event { } public int getBus() { - return mBus.getValue(); + return mBusId; } public int[] getInjections() { @@ -168,7 +174,7 @@ public class Event { + ", name=" + mName + ", vid=" + mVid + ", pid=" + mPid - + ", bus=" + mBus + + ", busId=" + mBusId + ", events=" + Arrays.toString(mInjections) + ", configuration=" + mConfiguration + ", duration=" + mDurationMillis + "ms" @@ -218,8 +224,8 @@ public class Event { mEvent.mPid = pid; } - public void setBus(Bus bus) { - mEvent.mBus = bus; + public void setBusId(int busId) { + mEvent.mBusId = busId; } public void setDurationMillis(int durationMillis) { diff --git a/cmds/uinput/src/com/android/commands/uinput/EventParser.java b/cmds/uinput/src/com/android/commands/uinput/EventParser.java new file mode 100644 index 000000000000..a4df03df7cd1 --- /dev/null +++ b/cmds/uinput/src/com/android/commands/uinput/EventParser.java @@ -0,0 +1,29 @@ +/* + * 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 java.io.IOException; + +/** + * Interface for a class that reads a stream of {@link Event}s. + */ +public interface EventParser { + /** + * Returns the next event in the file that the parser is reading from. + */ + Event getNextEvent() throws IOException; +} diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java index a2195c7809be..888ec5a1d33a 100644 --- a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java +++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java @@ -22,7 +22,7 @@ import android.util.Log; import android.util.SparseArray; import java.io.IOException; -import java.io.InputStreamReader; +import java.io.Reader; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -34,12 +34,12 @@ 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 { +public class JsonStyleParser implements EventParser { private static final String TAG = "UinputJsonStyleParser"; private JsonReader mReader; - public JsonStyleParser(InputStreamReader in) { + public JsonStyleParser(Reader in) { mReader = new JsonReader(in); mReader.setLenient(true); } @@ -62,7 +62,7 @@ public class JsonStyleParser { case "name" -> eb.setName(mReader.nextString()); case "vid" -> eb.setVid(readInt()); case "pid" -> eb.setPid(readInt()); - case "bus" -> eb.setBus(readBus()); + case "bus" -> eb.setBusId(readBus()); case "events" -> { int[] injections = readInjectedEvents().stream() .mapToInt(Integer::intValue).toArray(); @@ -139,9 +139,35 @@ public class JsonStyleParser { }); } - private Event.Bus readBus() throws IOException { + private int readBus() throws IOException { String val = mReader.nextString(); - return Event.Bus.valueOf(val.toUpperCase()); + // See include/uapi/linux/input.h in the kernel for the source of these constants. + return switch (val.toUpperCase()) { + case "PCI" -> 0x01; + case "ISAPNP" -> 0x02; + case "USB" -> 0x03; + case "HIL" -> 0x04; + case "BLUETOOTH" -> 0x05; + case "VIRTUAL" -> 0x06; + case "ISA" -> 0x10; + case "I8042" -> 0x11; + case "XTKBD" -> 0x12; + case "RS232" -> 0x13; + case "GAMEPORT" -> 0x14; + case "PARPORT" -> 0x15; + case "AMIGA" -> 0x16; + case "ADB" -> 0x17; + case "I2C" -> 0x18; + case "HOST" -> 0x19; + case "GSC" -> 0x1A; + case "ATARI" -> 0x1B; + case "SPI" -> 0x1C; + case "RMI" -> 0x1D; + case "CEC" -> 0x1E; + case "INTEL_ISHTP" -> 0x1F; + case "AMD_SFH" -> 0x20; + default -> throw new IllegalArgumentException("Invalid bus ID " + val); + }; } private SparseArray<int[]> readConfiguration() diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java index fe76abb9861d..684a12fc8f37 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java +++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java @@ -19,12 +19,12 @@ package com.android.commands.uinput; import android.util.Log; import android.util.SparseArray; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; import java.util.Objects; /** @@ -35,7 +35,7 @@ import java.util.Objects; public class Uinput { private static final String TAG = "UINPUT"; - private final JsonStyleParser mParser; + private final EventParser mParser; private final SparseArray<Device> mDevices; private static void usage() { @@ -74,12 +74,32 @@ public class Uinput { private Uinput(InputStream in) { mDevices = new SparseArray<Device>(); try { - mParser = new JsonStyleParser(new InputStreamReader(in, "UTF-8")); - } catch (UnsupportedEncodingException e) { + BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); + mParser = isEvemuFile(reader) ? new EvemuParser(reader) : new JsonStyleParser(reader); + } catch (IOException e) { throw new RuntimeException(e); } } + private boolean isEvemuFile(BufferedReader in) throws IOException { + // After zero or more empty lines (not even containing horizontal whitespace), evemu + // recordings must either start with '#' (indicating the EVEMU version header or a comment) + // or 'N' (for the name line). If we encounter anything else, assume it's a JSON-style input + // file. + + String lineSep = System.lineSeparator(); + char[] buf = new char[1]; + + in.mark(1 /* readAheadLimit */); + int charsRead = in.read(buf); + while (charsRead > 0 && lineSep.contains(String.valueOf(buf[0]))) { + in.mark(1 /* readAheadLimit */); + charsRead = in.read(buf); + } + in.reset(); + return buf[0] == '#' || buf[0] == 'N'; + } + private void run() { try { Event e = null; diff --git a/core/api/current.txt b/core/api/current.txt index f09036bc6162..812d4cd67ab7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12781,6 +12781,7 @@ package android.content.pm { method public boolean isPackageSuspended(); method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String); method public abstract boolean isSafeMode(); + method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull String, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException; method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String); method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String); method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int); @@ -33157,7 +33158,6 @@ package android.os { public static class PerformanceHintManager.Session implements java.io.Closeable { method public void close(); method public void reportActualWorkDuration(long); - method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public void reportActualWorkDuration(@NonNull android.os.WorkDuration); method @FlaggedApi("android.os.adpf_prefer_power_efficiency") public void setPreferPowerEfficiency(boolean); method public void setThreads(@NonNull int[]); method public void updateTargetWorkDuration(long); @@ -33500,7 +33500,6 @@ package android.os { method public static boolean setCurrentTimeMillis(long); method public static void sleep(long); method public static long uptimeMillis(); - method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static long uptimeNanos(); } public class TestLooperManager { @@ -33766,22 +33765,6 @@ package android.os { method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibration, @Nullable android.os.VibrationAttributes); } - @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public final class WorkDuration implements android.os.Parcelable { - ctor public WorkDuration(); - ctor public WorkDuration(long, long, long, long); - method public int describeContents(); - method public long getActualCpuDurationNanos(); - method public long getActualGpuDurationNanos(); - method public long getActualTotalDurationNanos(); - method public long getWorkPeriodStartTimestampNanos(); - method public void setActualCpuDurationNanos(long); - method public void setActualGpuDurationNanos(long); - method public void setActualTotalDurationNanos(long); - method public void setWorkPeriodStartTimestampNanos(long); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.os.WorkDuration> CREATOR; - } - public class WorkSource implements android.os.Parcelable { ctor public WorkSource(); ctor public WorkSource(android.os.WorkSource); @@ -36777,6 +36760,7 @@ package android.provider { field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS"; field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS"; field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS"; + field @FlaggedApi("android.app.modes_api") public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS"; field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS"; field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS"; field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL"; @@ -36864,6 +36848,7 @@ package android.provider { field public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled"; field public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE"; field public static final String EXTRA_AUTHORITIES = "authorities"; + field @FlaggedApi("android.app.modes_api") public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID"; field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled"; field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED"; field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST"; @@ -45177,6 +45162,7 @@ package android.telephony { method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid); method public boolean canManageSubscription(android.telephony.SubscriptionInfo); + method @FlaggedApi("com.android.internal.telephony.flags.work_profile_api_split") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>); method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context); method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList(); @@ -54201,7 +54187,7 @@ package android.view.accessibility { method public boolean isEnabled(); method public boolean isFocusable(); method public boolean isFocused(); - method public boolean isGranularScrollingSupported(); + method @FlaggedApi("android.view.accessibility.granular_scrolling") public boolean isGranularScrollingSupported(); method public boolean isHeading(); method public boolean isImportantForAccessibility(); method public boolean isLongClickable(); @@ -54251,7 +54237,7 @@ package android.view.accessibility { method public void setError(CharSequence); method public void setFocusable(boolean); method public void setFocused(boolean); - method public void setGranularScrollingSupported(boolean); + method @FlaggedApi("android.view.accessibility.granular_scrolling") public void setGranularScrollingSupported(boolean); method public void setHeading(boolean); method public void setHintText(CharSequence); method public void setImportantForAccessibility(boolean); @@ -54306,7 +54292,7 @@ package android.view.accessibility { field public static final String ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT = "android.view.accessibility.action.ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT"; field public static final String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE"; field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT"; - field public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT"; + field @FlaggedApi("android.view.accessibility.granular_scrolling") public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT"; field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT"; field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT"; field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE"; @@ -54409,10 +54395,9 @@ package android.view.accessibility { public static final class AccessibilityNodeInfo.CollectionInfo { ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean); ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int); - ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int, int, int); method public int getColumnCount(); - method public int getImportantForAccessibilityItemCount(); - method public int getItemCount(); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getImportantForAccessibilityItemCount(); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getItemCount(); method public int getRowCount(); method public int getSelectionMode(); method public boolean isHierarchical(); @@ -54421,18 +54406,18 @@ package android.view.accessibility { field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2 field public static final int SELECTION_MODE_NONE = 0; // 0x0 field public static final int SELECTION_MODE_SINGLE = 1; // 0x1 - field public static final int UNDEFINED = -1; // 0xffffffff - } - - public static final class AccessibilityNodeInfo.CollectionInfo.Builder { - ctor public AccessibilityNodeInfo.CollectionInfo.Builder(); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build(); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int); - method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int); + field @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final int UNDEFINED = -1; // 0xffffffff + } + + @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final class AccessibilityNodeInfo.CollectionInfo.Builder { + ctor @FlaggedApi("android.view.accessibility.collection_info_item_counts") public AccessibilityNodeInfo.CollectionInfo.Builder(); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build(); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int); + method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int); } public static final class AccessibilityNodeInfo.CollectionItemInfo { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 32d252ebda29..c282e4b6f3eb 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -307,7 +307,7 @@ package android { field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE"; field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER"; field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER"; - field public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE"; + field @FlaggedApi("com.android.net.flags.register_nsd_offload_engine") public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE"; field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION"; field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM"; field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER"; @@ -3868,8 +3868,9 @@ package android.content.pm { public class PackageInstaller { method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException; + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; - method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean); field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL"; field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL"; @@ -3883,12 +3884,20 @@ package android.content.pm { field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID"; field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; + field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS"; field public static final int LOCATION_DATA_APP = 0; // 0x0 field public static final int LOCATION_MEDIA_DATA = 2; // 0x2 field public static final int LOCATION_MEDIA_OBB = 1; // 0x1 field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0 field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1 field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64 + field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0 } public static class PackageInstaller.InstallInfo { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index bb335fae887b..6c10f495e7bf 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIO import static android.Manifest.permission.DETECT_SCREEN_CAPTURE; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.os.Process.myUid; @@ -7603,15 +7604,17 @@ public class Activity extends ContextThemeWrapper * @param taskDescription The TaskDescription properties that describe the task with this activity */ public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { - if (mTaskDescription != taskDescription) { - mTaskDescription.copyFromPreserveHiddenFields(taskDescription); - // Scale the icon down to something reasonable if it is provided - if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) { - final int size = ActivityManager.getLauncherLargeIconSizeInner(this); - final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size, - true); - mTaskDescription.setIcon(Icon.createWithBitmap(icon)); - } + if (taskDescription == null || mTaskDescription.equals(taskDescription)) { + return; + } + + mTaskDescription.copyFromPreserveHiddenFields(taskDescription); + // Scale the icon down to something reasonable if it is provided + if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) { + final int size = ActivityManager.getLauncherLargeIconSizeInner(this); + final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size, + true); + mTaskDescription.setIcon(Icon.createWithBitmap(icon)); } ActivityClient.getInstance().setTaskDescription(mToken, mTaskDescription); } @@ -9439,6 +9442,15 @@ public class Activity extends ContextThemeWrapper ActivityClient.getInstance().enableTaskLocaleOverride(mToken); } + /** + * Request ActivityRecordInputSink to enable or disable blocking input events. + * @hide + */ + @RequiresPermission(INTERNAL_SYSTEM_WINDOW) + public void setActivityRecordInputSinkEnabled(boolean enabled) { + ActivityClient.getInstance().setActivityRecordInputSinkEnabled(mToken, enabled); + } + class HostCallbacks extends FragmentHostCallback<Activity> { public HostCallbacks() { super(Activity.this /*activity*/); diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index b35e87b541d3..b8bd030872c1 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -16,6 +16,8 @@ package android.app; +import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; + import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.content.ComponentName; @@ -614,6 +616,15 @@ public class ActivityClient { } } + @RequiresPermission(INTERNAL_SYSTEM_WINDOW) + void setActivityRecordInputSinkEnabled(IBinder activityToken, boolean enabled) { + try { + getActivityClientController().setActivityRecordInputSinkEnabled(activityToken, enabled); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + /** * Shows or hides a Camera app compat toggle for stretched issues with the requested state. * diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 02eaf0b3bbd3..8af12161cc08 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -232,6 +232,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; import com.android.org.conscrypt.TrustedCertificateStore; import com.android.server.am.MemInfoDumpProto; +import com.android.window.flags.Flags; import dalvik.annotation.optimization.NeverCompile; import dalvik.system.AppSpecializationHooks; @@ -3713,7 +3714,13 @@ public final class ActivityThread extends ClientTransactionHandler final ArrayList<ResultInfo> list = new ArrayList<>(); list.add(new ResultInfo(id, requestCode, resultCode, data)); final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread); - clientTransaction.addCallback(ActivityResultItem.obtain(activityToken, list)); + final ActivityResultItem activityResultItem = ActivityResultItem.obtain( + activityToken, list); + if (Flags.bundleClientTransactionFlag()) { + clientTransaction.addTransactionItem(activityResultItem); + } else { + clientTransaction.addCallback(activityResultItem); + } try { mAppThread.scheduleTransaction(clientTransaction); } catch (RemoteException e) { @@ -4492,16 +4499,26 @@ public final class ActivityThread extends ClientTransactionHandler private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) { final ClientTransaction transaction = ClientTransaction.obtain(mAppThread); - transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.token, + final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token, r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags, - /* dontReport */ false, /* autoEnteringPip */ false)); + /* dontReport */ false, /* autoEnteringPip */ false); + if (Flags.bundleClientTransactionFlag()) { + transaction.addTransactionItem(pauseActivityItem); + } else { + transaction.setLifecycleStateRequest(pauseActivityItem); + } executeTransaction(transaction); } private void scheduleResume(ActivityClientRecord r) { final ClientTransaction transaction = ClientTransaction.obtain(mAppThread); - transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(r.token, - /* isForward */ false, /* shouldSendCompatFakeFocus */ false)); + final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(r.token, + /* isForward */ false, /* shouldSendCompatFakeFocus */ false); + if (Flags.bundleClientTransactionFlag()) { + transaction.addTransactionItem(resumeActivityItem); + } else { + transaction.setLifecycleStateRequest(resumeActivityItem); + } executeTransaction(transaction); } @@ -6092,8 +6109,13 @@ public final class ActivityThread extends ClientTransactionHandler TransactionExecutorHelper.getLifecycleRequestForCurrentState(r); // Schedule the transaction. final ClientTransaction transaction = ClientTransaction.obtain(mAppThread); - transaction.addCallback(activityRelaunchItem); - transaction.setLifecycleStateRequest(lifecycleRequest); + if (Flags.bundleClientTransactionFlag()) { + transaction.addTransactionItem(activityRelaunchItem); + transaction.addTransactionItem(lifecycleRequest); + } else { + transaction.addCallback(activityRelaunchItem); + transaction.setLifecycleStateRequest(lifecycleRequest); + } executeTransaction(transaction); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index ca6d8df1df12..a4c3bb824502 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -80,6 +80,8 @@ import android.content.pm.SuspendDialogInfo; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; import android.content.pm.dex.ArtManager; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.res.ApkAssets; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.XmlResourceParser; @@ -144,6 +146,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.function.Function; /** @hide */ public class ApplicationPackageManager extends PackageManager { @@ -4024,4 +4027,34 @@ public class ApplicationPackageManager extends PackageManager { throw e.rethrowFromSystemServer(); } } + + @Override + public <T> T parseAndroidManifest(@NonNull String apkFilePath, + @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException { + Objects.requireNonNull(apkFilePath, "apkFilePath cannot be null"); + Objects.requireNonNull(parserFunction, "parserFunction cannot be null"); + try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFilePath)) { + return parserFunction.apply(xmlResourceParser); + } catch (IOException e) { + Log.w(TAG, "Failed to get the android manifest parser", e); + throw e; + } + } + + private static XmlResourceParser getAndroidManifestParser(@NonNull String apkFilePath) + throws IOException { + ApkAssets apkAssets = null; + try { + apkAssets = ApkAssets.loadFromPath(apkFilePath); + return apkAssets.openXml(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME); + } finally { + if (apkAssets != null) { + try { + apkAssets.close(); + } catch (Throwable ignored) { + Log.w(TAG, "Failed to close apkAssets", ignored); + } + } + } + } } diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index a3c5e1c67e1b..7370fc36c23e 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -191,4 +191,14 @@ interface IActivityClientController { */ boolean isRequestedToLaunchInTaskFragment(in IBinder activityToken, in IBinder taskFragmentToken); + + /** + * Enable or disable ActivityRecordInputSink to block input events. + * + * @param token The token for the activity that requests to toggle. + * @param enabled Whether the input evens are blocked by ActivityRecordInputSink. + */ + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.INTERNAL_SYSTEM_WINDOW)") + oneway void setActivityRecordInputSinkEnabled(in IBinder activityToken, boolean enabled); } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 56d0d1f2843d..6c9c14fbc83a 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -51,6 +51,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; import android.os.UserHandle; +import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Adjustment; import android.service.notification.Condition; @@ -1253,7 +1254,8 @@ public class NotificationManager { * <p> * If this method returns true, calls to * {@link #updateAutomaticZenRule(String, AutomaticZenRule)} may fail and apps should defer - * rule management to system settings/uis. + * rule management to system settings/uis via + * {@link Settings#ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}. */ @FlaggedApi(Flags.FLAG_MODES_API) public boolean areAutomaticZenRulesUserManaged() { diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index cc56a1cee7a2..d8448dcb4f9c 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -72,6 +72,7 @@ per-file *Notification* = file:/packages/SystemUI/OWNERS per-file *Zen* = file:/packages/SystemUI/OWNERS per-file *StatusBar* = file:/packages/SystemUI/OWNERS per-file *UiModeManager* = file:/packages/SystemUI/OWNERS +per-file notification.aconfig = file:/packages/SystemUI/OWNERS # PackageManager per-file ApplicationPackageManager.java = file:/services/core/java/com/android/server/pm/OWNERS diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index bbd07b8c21ea..35ce10223aa6 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -48,3 +48,10 @@ flag { description: "Update DumpSys to include information about migrated APIs in DPE" bug: "304999634" } + +flag { + name: "quiet_mode_credential_bug_fix" + namespace: "enterprise" + description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped." + bug: "293441361" +} diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 7c34cdefe95f..5e5526802fe9 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -75,7 +75,6 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** * Adds a message to the end of the sequence of transaction items. * @param item A single message that can contain a client activity/window request/callback. - * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}. */ public void addTransactionItem(@NonNull ClientTransactionItem item) { if (mTransactionItems == null) { diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java index dfbccb41d045..475c6fb9a48a 100644 --- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java +++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java @@ -235,7 +235,9 @@ public class TransactionExecutorHelper { * Configuration - ActivityResult - Configuration - ActivityResult * index 1 will be returned, because ActivityResult request on position 1 will be the last * request that moves activity to the RESUMED state where it will eventually end. + * @deprecated to be removed with {@link TransactionExecutor#executeCallbacks}. */ + @Deprecated static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) { final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); if (callbacks == null || callbacks.isEmpty() diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING index 49a4467b1f67..47a152aa6cca 100644 --- a/core/java/android/app/time/TEST_MAPPING +++ b/core/java/android/app/time/TEST_MAPPING @@ -1,6 +1,5 @@ { - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + "presubmit": [ { "name": "FrameworksTimeCoreTests", "options": [ @@ -8,7 +7,10 @@ "include-filter": "android.app." } ] - }, + } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ { "name": "FrameworksServicesTests", "options": [ diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING index c050a55a3e18..9517fb99b04a 100644 --- a/core/java/android/app/timedetector/TEST_MAPPING +++ b/core/java/android/app/timedetector/TEST_MAPPING @@ -1,6 +1,5 @@ { - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + "presubmit": [ { "name": "FrameworksTimeCoreTests", "options": [ @@ -8,7 +7,10 @@ "include-filter": "android.app." } ] - }, + } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ { "name": "FrameworksServicesTests", "options": [ diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING index 46656d125b70..fd41b869efaf 100644 --- a/core/java/android/app/timezonedetector/TEST_MAPPING +++ b/core/java/android/app/timezonedetector/TEST_MAPPING @@ -1,6 +1,5 @@ { - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + "presubmit": [ { "name": "FrameworksTimeCoreTests", "options": [ @@ -8,7 +7,10 @@ "include-filter": "android.app." } ] - }, + } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ { "name": "FrameworksServicesTests", "options": [ diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java index 016d97f60e26..aa3239237478 100644 --- a/core/java/android/app/usage/ParcelableUsageEventList.java +++ b/core/java/android/app/usage/ParcelableUsageEventList.java @@ -48,13 +48,16 @@ public final class ParcelableUsageEventList implements Parcelable { private List<Event> mList; - public ParcelableUsageEventList(List<Event> list) { + public ParcelableUsageEventList(@NonNull List<Event> list) { + if (list == null) { + throw new IllegalArgumentException("Empty list"); + } mList = list; } private ParcelableUsageEventList(Parcel in) { final int N = in.readInt(); - mList = new ArrayList<>(); + mList = new ArrayList<>(N); if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); if (N <= 0) { return; diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 1eb452cfd085..6c7eba06dea7 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -24,9 +24,11 @@ import android.content.res.Configuration; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -35,6 +37,7 @@ import java.util.List; * from which to read {@link android.app.usage.UsageEvents.Event} objects. */ public final class UsageEvents implements Parcelable { + private static final String TAG = "UsageEvents"; /** @hide */ public static final String INSTANT_APP_PACKAGE_NAME = "android.instant_app"; @@ -786,10 +789,20 @@ public final class UsageEvents implements Parcelable { } private void readUsageEventsFromParcelWithParceledList(Parcel in) { + mEventCount = in.readInt(); mIndex = in.readInt(); - mEventsToWrite = in.readParcelable(UsageEvents.class.getClassLoader(), - ParcelableUsageEventList.class).getList(); - mEventCount = mEventsToWrite.size(); + ParcelableUsageEventList slice = in.readParcelable(getClass().getClassLoader(), + ParcelableUsageEventList.class); + if (slice != null) { + mEventsToWrite = slice.getList(); + } else { + mEventsToWrite = new ArrayList<>(); + } + + if (mEventCount != mEventsToWrite.size()) { + Log.w(TAG, "Partial usage event list received: " + mEventCount + " != " + + mEventsToWrite.size()); + } } private void readUsageEventsFromParcelWithBlob(Parcel in) { @@ -1065,6 +1078,7 @@ public final class UsageEvents implements Parcelable { } private void writeUsageEventsToParcelWithParceledList(Parcel dest, int flags) { + dest.writeInt(mEventCount); dest.writeInt(mIndex); dest.writeParcelable(new ParcelableUsageEventList(mEventsToWrite), flags); } diff --git a/core/java/android/companion/utils/FeatureUtils.java b/core/java/android/companion/utils/FeatureUtils.java deleted file mode 100644 index a382e09ae7b2..000000000000 --- a/core/java/android/companion/utils/FeatureUtils.java +++ /dev/null @@ -1,52 +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.utils; - -import android.os.Binder; -import android.os.Build; -import android.provider.DeviceConfig; - -/** - * Util class for feature flags - * - * @hide - */ -public final class FeatureUtils { - - private static final String NAMESPACE_COMPANION = "companion"; - - private static final String PROPERTY_PERM_SYNC_ENABLED = "perm_sync_enabled"; - - public static boolean isPermSyncEnabled() { - // Permissions sync is always enabled in debuggable mode. - if (Build.isDebuggable()) { - return true; - } - - // Clear app identity to read the device config for feature flag. - final long identity = Binder.clearCallingIdentity(); - try { - return DeviceConfig.getBoolean(NAMESPACE_COMPANION, - PROPERTY_PERM_SYNC_ENABLED, false); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - private FeatureUtils() { - } -} diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index 59ed0453bc01..1f25fd039dd8 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -16,6 +16,7 @@ package android.content.pm; +import android.app.PendingIntent; import android.content.pm.ArchivedPackageParcel; import android.content.pm.IPackageDeleteObserver2; import android.content.pm.IPackageInstallerCallback; @@ -82,7 +83,7 @@ interface IPackageInstaller { void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})") - void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle); + void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)") void installPackageArchived(in ArchivedPackageParcel archivedPackageParcel, @@ -90,4 +91,6 @@ interface IPackageInstaller { in IntentSender statusReceiver, String installerPackageName, in UserHandle userHandle); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})") + void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index e9a2aaad6579..4f0bfc7437c7 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -387,6 +387,24 @@ public class PackageInstaller { "android.content.pm.extra.UNARCHIVE_ALL_USERS"; /** + * Current status of an unarchive operation. Will be one of + * {@link #UNARCHIVAL_OK}, {@link #UNARCHIVAL_ERROR_USER_ACTION_NEEDED}, + * {@link #UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE}, {@link #UNARCHIVAL_ERROR_NO_CONNECTIVITY}, + * {@link #UNARCHIVAL_GENERIC_ERROR}, {@link #UNARCHIVAL_ERROR_INSTALLER_DISABLED} or + * {@link #UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED}. + * + * <p> If the status is not {@link #UNARCHIVAL_OK}, then {@link Intent#EXTRA_INTENT} will be set + * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a + * failure dialog. + * + * @see #requestUnarchive + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS"; + + /** * A list of warnings that occurred during installation. * * @hide @@ -652,6 +670,102 @@ public class PackageInstaller { @Retention(RetentionPolicy.SOURCE) public @interface UserActionReason {} + /** + * The unarchival is possible and will commence. + * + * <p> Note that this does not mean that the unarchival has completed. This status should be + * sent before any longer asynchronous action (e.g. app download) is started. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_OK = 0; + + /** + * The user needs to interact with the installer to enable the installation. + * + * <p> An example use case for this could be that the user needs to login to allow the + * download for a paid app. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; + + /** + * Not enough storage to unarchive the application. + * + * <p> The installer can optionally provide a {@code userActionIntent} for a space-clearing + * dialog. If no action is provided, then a generic intent + * {@link android.os.storage.StorageManager#ACTION_MANAGE_STORAGE} is started instead. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; + + /** + * The device is not connected to the internet + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; + + /** + * The installer responsible for the unarchival is disabled. + * + * <p> Should only be used by the system. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; + + /** + * The installer responsible for the unarchival has been uninstalled + * + * <p> Should only be used by the system. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; + + /** + * Generic error: The app cannot be unarchived. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final int UNARCHIVAL_GENERIC_ERROR = 100; + + /** + * The set of error types that can be set for + * {@link #reportUnarchivalStatus(int, int, PendingIntent)}. + * + * @hide + */ + @IntDef(value = { + UNARCHIVAL_OK, + UNARCHIVAL_ERROR_USER_ACTION_NEEDED, + UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE, + UNARCHIVAL_ERROR_NO_CONNECTIVITY, + UNARCHIVAL_ERROR_INSTALLER_DISABLED, + UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED, + UNARCHIVAL_GENERIC_ERROR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UnarchivalStatus {} + + /** Default set of checksums - includes all available checksums. * @see Session#requestChecksums */ private static final int DEFAULT_CHECKSUMS = @@ -2238,8 +2352,8 @@ public class PackageInstaller { * Requests to archive a package which is currently installed. * * <p> During the archival process, the apps APKs and cache are removed from the device while - * the user data is kept. Through the {@link #requestUnarchive(String)} call, apps can be - * restored again through their responsible installer. + * the user data is kept. Through the {@link #requestUnarchive} call, apps + * can be restored again through their responsible installer. * * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and * will be displayed to users with UI treatment to highlight that said apps are archived. If @@ -2278,6 +2392,10 @@ public class PackageInstaller { * <p> The installation will happen asynchronously and can be observed through * {@link android.content.Intent#ACTION_PACKAGE_ADDED}. * + * @param statusReceiver Callback used to notify whether the installer has accepted the + * unarchival request or an error has occurred. The status update will be + * sent though {@link EXTRA_UNARCHIVE_STATUS}. Only one status will be + * sent. * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not * visible to the caller or if the package has no * installer on the device anymore to unarchive it. @@ -2290,10 +2408,10 @@ public class PackageInstaller { Manifest.permission.REQUEST_INSTALL_PACKAGES}) @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) - public void requestUnarchive(@NonNull String packageName) + public void requestUnarchive(@NonNull String packageName, @NonNull IntentSender statusReceiver) throws IOException, PackageManager.NameNotFoundException { try { - mInstaller.requestUnarchive(packageName, mInstallerPackageName, + mInstaller.requestUnarchive(packageName, mInstallerPackageName, statusReceiver, new UserHandle(mUserId)); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); @@ -2303,6 +2421,39 @@ public class PackageInstaller { } } + /** + * Reports the status of an unarchival to the system. + * + * @param unarchiveId the ID provided by the system as part of the + * intent.action.UNARCHIVE broadcast with EXTRA_UNARCHIVE_ID. + * @param status is used for the system to provide the user with necessary + * follow-up steps or errors. + * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field + * should be set to specify how many additional bytes of storage + * are required to unarchive the app. + * @param userActionIntent Optional intent to start a follow up action required to + * facilitate the unarchival flow (e.g. user needs to log in). + * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists + * @hide + */ + @RequiresPermission(anyOf = { + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.REQUEST_INSTALL_PACKAGES}) + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public void reportUnarchivalStatus(int unarchiveId, @UnarchivalStatus int status, + long requiredStorageBytes, @Nullable PendingIntent userActionIntent) + throws PackageManager.NameNotFoundException { + try { + mInstaller.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes, + userActionIntent, new UserHandle(mUserId)); + } catch (ParcelableException e) { + e.maybeRethrow(PackageManager.NameNotFoundException.class); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + // (b/239722738) This class serves as a bridge between the PackageLite class, which // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java) // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or @@ -2566,6 +2717,8 @@ public class PackageInstaller { public int developmentInstallFlags = 0; /** {@hide} */ public int unarchiveId = -1; + /** {@hide} */ + public IntentSender unarchiveIntentSender; private final ArrayMap<String, Integer> mPermissionStates; @@ -2618,6 +2771,7 @@ public class PackageInstaller { applicationEnabledSettingPersistent = source.readBoolean(); developmentInstallFlags = source.readInt(); unarchiveId = source.readInt(); + unarchiveIntentSender = source.readParcelable(null, IntentSender.class); } /** {@hide} */ @@ -2652,6 +2806,7 @@ public class PackageInstaller { ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent; ret.developmentInstallFlags = developmentInstallFlags; ret.unarchiveId = unarchiveId; + ret.unarchiveIntentSender = unarchiveIntentSender; return ret; } @@ -3364,6 +3519,7 @@ public class PackageInstaller { applicationEnabledSettingPersistent); pw.printHexPair("developmentInstallFlags", developmentInstallFlags); pw.printPair("unarchiveId", unarchiveId); + pw.printPair("unarchiveIntentSender", unarchiveIntentSender); pw.println(); } @@ -3408,6 +3564,7 @@ public class PackageInstaller { dest.writeBoolean(applicationEnabledSettingPersistent); dest.writeInt(developmentInstallFlags); dest.writeInt(unarchiveId); + dest.writeParcelable(unarchiveIntentSender, flags); } public static final Parcelable.Creator<SessionParams> diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c3b3423c1a57..fe31c9dbf27d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -34,6 +34,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; +import android.annotation.WorkerThread; import android.annotation.XmlRes; import android.app.ActivityManager; import android.app.ActivityThread; @@ -96,6 +97,7 @@ import com.android.internal.util.DataClass; import dalvik.system.VMRuntime; import java.io.File; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.cert.Certificate; @@ -108,6 +110,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.function.Function; /** * Class for retrieving various kinds of information related to the application @@ -11426,4 +11429,60 @@ public abstract class PackageManager { throw new UnsupportedOperationException( "unregisterPackageMonitorCallback not implemented in subclass"); } + + /** + * Retrieve AndroidManifest.xml information for the given application apk path. + * + * <p>Example: + * + * <pre><code> + * Bundle result; + * try { + * result = getContext().getPackageManager().parseAndroidManifest(apkFilePath, + * xmlResourceParser -> { + * Bundle bundle = new Bundle(); + * // Search the start tag + * int type; + * while ((type = xmlResourceParser.next()) != XmlPullParser.START_TAG + * && type != XmlPullParser.END_DOCUMENT) { + * } + * if (type != XmlPullParser.START_TAG) { + * return bundle; + * } + * + * // Start to read the tags and attributes from the xmlResourceParser + * if (!xmlResourceParser.getName().equals("manifest")) { + * return bundle; + * } + * String packageName = xmlResourceParser.getAttributeValue(null, "package"); + * bundle.putString("package", packageName); + * + * // Continue to read the tags and attributes from the xmlResourceParser + * + * return bundle; + * }); + * } catch (IOException e) { + * } + * </code></pre> + * + * Note: When the parserFunction is invoked, the client can read the AndroidManifest.xml + * information by the XmlResourceParser object. After leaving the parserFunction, the + * XmlResourceParser object will be closed. + * + * @param apkFilePath The path of an application apk file. + * @param parserFunction The parserFunction will be invoked with the XmlResourceParser object + * after getting the AndroidManifest.xml of an application package. + * + * @return Returns the result of the {@link Function#apply(Object)}. + * + * @throws IOException if the AndroidManifest.xml of an application package cannot be + * read or accessed. + */ + @FlaggedApi(android.content.pm.Flags.FLAG_GET_PACKAGE_INFO) + @WorkerThread + public <T> T parseAndroidManifest(@NonNull String apkFilePath, + @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException { + throw new UnsupportedOperationException( + "parseAndroidManifest not implemented in subclass"); + } } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 89f889f988e2..fe95a2ab8e6d 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1414,7 +1414,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * otherwise the value will be equal to 1. * Note that this level is just a number of supported levels (the granularity of control). * There is no actual physical power units tied to this level.</p> - * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p>This key is available on all devices.</p> * * @see CaptureRequest#FLASH_MODE */ @@ -1430,7 +1430,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * or equal to <code>android.flash.info.singleStrengthMaxLevel</code>. * Note for devices that do not support the manual flash strength control * feature, this level will always be equal to 1.</p> - * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p>This key is available on all devices.</p> */ @PublicKey @NonNull @@ -1450,7 +1450,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * android.flash.info.singleStrengthMaxLevel i.e. the ratio of * android.flash.info.torchStrengthMaxLevel:android.flash.info.singleStrengthMaxLevel * is not guaranteed to be the ratio of actual brightness.</p> - * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p>This key is available on all devices.</p> * * @see CaptureRequest#FLASH_MODE */ @@ -1466,7 +1466,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * or equal to android.flash.info.torchStrengthMaxLevel. * Note for the devices that do not support the manual flash strength control feature, * this level will always be equal to 1.</p> - * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p>This key is available on all devices.</p> */ @PublicKey @NonNull diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 5d0697806512..93cae545deab 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -2688,7 +2688,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * set to TORCH; * <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is * set to SINGLE</p> - * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p>This key is available on all devices.</p> * * @see CaptureRequest#CONTROL_AE_MODE * @see CaptureRequest#FLASH_MODE diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 0d204f3ececa..12ab0f6e50e1 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2974,7 +2974,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * set to TORCH; * <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is * set to SINGLE</p> - * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p>This key is available on all devices.</p> * * @see CaptureRequest#CONTROL_AE_MODE * @see CaptureRequest#FLASH_MODE diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java index 7756b9ca7e5a..c3791888b44f 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java +++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java @@ -22,9 +22,11 @@ import android.annotation.RequiresPermission; import android.content.Context; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.Trace; import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; @@ -45,6 +47,8 @@ import java.util.concurrent.Executor; @VisibleForTesting(visibility = Visibility.PACKAGE) public final class DeviceStateManagerGlobal { private static DeviceStateManagerGlobal sInstance; + private static final String TAG = "DeviceStateManagerGlobal"; + private static final boolean DEBUG = Build.IS_DEBUGGABLE; /** * Returns an instance of {@link DeviceStateManagerGlobal}. May return {@code null} if a @@ -400,11 +404,29 @@ public final class DeviceStateManagerGlobal { } void notifyBaseStateChanged(int newBaseState) { - mExecutor.execute(() -> mDeviceStateCallback.onBaseStateChanged(newBaseState)); + execute("notifyBaseStateChanged", + () -> mDeviceStateCallback.onBaseStateChanged(newBaseState)); } void notifyStateChanged(int newDeviceState) { - mExecutor.execute(() -> mDeviceStateCallback.onStateChanged(newDeviceState)); + execute("notifyStateChanged", + () -> mDeviceStateCallback.onStateChanged(newDeviceState)); + } + + private void execute(String traceName, Runnable r) { + mExecutor.execute(() -> { + if (DEBUG) { + Trace.beginSection( + mDeviceStateCallback.getClass().getSimpleName() + "#" + traceName); + } + try { + r.run(); + } finally { + if (DEBUG) { + Trace.endSection(); + } + } + }); } } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 4791a8341912..f71e853a1170 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -721,8 +721,19 @@ public abstract class DisplayManagerInternal { public interface DisplayOffloadSession { /** Provide the display state to use in place of state DOZE. */ void setDozeStateOverride(int displayState); - /** Returns the associated DisplayOffloader. */ - DisplayOffloader getDisplayOffloader(); + + /** Whether the session is active. */ + boolean isActive(); + + /** + * Update the brightness from the offload chip. + * @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and + * {@link PowerManager.BRIGHTNESS_MAX}, or + * {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} which removes + * the brightness from offload. Other values will be ignored. + */ + void updateBrightness(float brightness); + /** Returns whether displayoffload supports the given display state. */ static boolean isSupportedOffloadState(int displayState) { return Display.isSuspendedState(displayState); diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java index bb3e81ad0ec1..7ac1dd14da21 100644 --- a/core/java/android/hardware/location/NanoAppMessage.java +++ b/core/java/android/hardware/location/NanoAppMessage.java @@ -56,6 +56,8 @@ public final class NanoAppMessage implements Parcelable { * * @param targetNanoAppId the ID of the nanoapp to send the message to * @param messageType the nanoapp-dependent message type + * the value CHRE_MESSAGE_TYPE_RPC (0x7FFFFFF5) is reserved by the + * framework for RPC messages * @param messageBody the byte array message contents * * @return the NanoAppMessage object diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index f817fb8dcaed..1100731702a2 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -82,8 +82,8 @@ public final class BinderProxy implements IBinder { private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE; private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1; /** - * Debuggable builds will throw an BinderProxyMapSizeException if the number of - * map entries exceeds: + * We will throw a BinderProxyMapSizeException if the number of map entries + * exceeds: */ private static final int CRASH_AT_SIZE = 25_000; diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl index fe85da26e610..6b43e73d10e7 100644 --- a/core/java/android/os/IHintSession.aidl +++ b/core/java/android/os/IHintSession.aidl @@ -17,8 +17,6 @@ package android.os; -import android.os.WorkDuration; - /** {@hide} */ oneway interface IHintSession { void updateTargetWorkDuration(long targetDurationNanos); @@ -26,5 +24,4 @@ oneway interface IHintSession { void close(); void sendHint(int hint); void setMode(int mode, boolean enabled); - void reportActualWorkDuration2(in WorkDuration[] workDurations); } diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java index e0059105c21f..11084b88fad1 100644 --- a/core/java/android/os/PerformanceHintManager.java +++ b/core/java/android/os/PerformanceHintManager.java @@ -103,7 +103,7 @@ public final class PerformanceHintManager { * Any call in this class will change its internal data, so you must do your own thread * safety to protect from racing. * - * All timings should be in {@link SystemClock#uptimeNanos()}. + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. */ public static class Session implements Closeable { private long mNativeSessionPtr; @@ -269,40 +269,6 @@ public final class PerformanceHintManager { public @Nullable int[] getThreadIds() { return nativeGetThreadIds(mNativeSessionPtr); } - - /** - * Reports the work duration for the last cycle of work. - * - * The system will attempt to adjust the core placement of the threads within the thread - * group and/or the frequency of the core on which they are run to bring the actual duration - * close to the target duration. - * - * @param workDuration the work duration of each component. - * @throws IllegalArgumentException if work period start timestamp is not positive, or - * actual total duration is not positive, or actual CPU duration is not positive, - * or actual GPU duration is negative. - */ - @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) - public void reportActualWorkDuration(@NonNull WorkDuration workDuration) { - if (workDuration.mWorkPeriodStartTimestampNanos <= 0) { - throw new IllegalArgumentException( - "the work period start timestamp should be positive."); - } - if (workDuration.mActualTotalDurationNanos <= 0) { - throw new IllegalArgumentException("the actual total duration should be positive."); - } - if (workDuration.mActualCpuDurationNanos <= 0) { - throw new IllegalArgumentException("the actual CPU duration should be positive."); - } - if (workDuration.mActualGpuDurationNanos < 0) { - throw new IllegalArgumentException( - "the actual GPU duration should be non negative."); - } - nativeReportActualWorkDuration(mNativeSessionPtr, - workDuration.mWorkPeriodStartTimestampNanos, - workDuration.mActualTotalDurationNanos, - workDuration.mActualCpuDurationNanos, workDuration.mActualGpuDurationNanos); - } } private static native long nativeAcquireManager(); @@ -319,7 +285,4 @@ public final class PerformanceHintManager { private static native void nativeSetThreads(long nativeSessionPtr, int[] tids); private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr, boolean enabled); - private static native void nativeReportActualWorkDuration(long nativeSessionPtr, - long workPeriodStartTimestampNanos, long actualTotalDurationNanos, - long actualCpuDurationNanos, long actualGpuDurationNanos); } diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index e2a58338230c..49a0bd3289aa 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -16,7 +16,6 @@ package android.os; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.app.IAlarmManager; import android.app.time.UnixEpochTime; @@ -203,8 +202,8 @@ public final class SystemClock { * Returns nanoseconds since boot, not counting time spent in deep sleep. * * @return nanoseconds of non-sleep uptime since boot. + * @hide */ - @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) @CriticalNative @android.ravenwood.annotation.RavenwoodReplace public static native long uptimeNanos(); diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java deleted file mode 100644 index 4fdc34fb60d1..000000000000 --- a/core/java/android/os/WorkDuration.java +++ /dev/null @@ -1,213 +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.os; - -import android.annotation.FlaggedApi; -import android.annotation.NonNull; - -import java.util.Objects; - -/** - * WorkDuration contains the measured time in nano seconds of the workload - * in each component, see - * {@link PerformanceHintManager.Session#reportActualWorkDuration(WorkDuration)}. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ -@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) -public final class WorkDuration implements Parcelable { - long mWorkPeriodStartTimestampNanos = 0; - long mActualTotalDurationNanos = 0; - long mActualCpuDurationNanos = 0; - long mActualGpuDurationNanos = 0; - long mTimestampNanos = 0; - - public static final @NonNull Creator<WorkDuration> CREATOR = new Creator<>() { - @Override - public WorkDuration createFromParcel(Parcel in) { - return new WorkDuration(in); - } - - @Override - public WorkDuration[] newArray(int size) { - return new WorkDuration[size]; - } - }; - - public WorkDuration() {} - - public WorkDuration(long workPeriodStartTimestampNanos, - long actualTotalDurationNanos, - long actualCpuDurationNanos, - long actualGpuDurationNanos) { - mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; - mActualTotalDurationNanos = actualTotalDurationNanos; - mActualCpuDurationNanos = actualCpuDurationNanos; - mActualGpuDurationNanos = actualGpuDurationNanos; - } - - /** - * @hide - */ - public WorkDuration(long workPeriodStartTimestampNanos, - long actualTotalDurationNanos, - long actualCpuDurationNanos, - long actualGpuDurationNanos, - long timestampNanos) { - mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; - mActualTotalDurationNanos = actualTotalDurationNanos; - mActualCpuDurationNanos = actualCpuDurationNanos; - mActualGpuDurationNanos = actualGpuDurationNanos; - mTimestampNanos = timestampNanos; - } - - WorkDuration(@NonNull Parcel in) { - mWorkPeriodStartTimestampNanos = in.readLong(); - mActualTotalDurationNanos = in.readLong(); - mActualCpuDurationNanos = in.readLong(); - mActualGpuDurationNanos = in.readLong(); - mTimestampNanos = in.readLong(); - } - - /** - * Sets the work period start timestamp in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) { - if (workPeriodStartTimestampNanos <= 0) { - throw new IllegalArgumentException( - "the work period start timestamp should be positive."); - } - mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; - } - - /** - * Sets the actual total duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setActualTotalDurationNanos(long actualTotalDurationNanos) { - if (actualTotalDurationNanos <= 0) { - throw new IllegalArgumentException("the actual total duration should be positive."); - } - mActualTotalDurationNanos = actualTotalDurationNanos; - } - - /** - * Sets the actual CPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setActualCpuDurationNanos(long actualCpuDurationNanos) { - if (actualCpuDurationNanos <= 0) { - throw new IllegalArgumentException("the actual CPU duration should be positive."); - } - mActualCpuDurationNanos = actualCpuDurationNanos; - } - - /** - * Sets the actual GPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setActualGpuDurationNanos(long actualGpuDurationNanos) { - if (actualGpuDurationNanos < 0) { - throw new IllegalArgumentException("the actual GPU duration should be non negative."); - } - mActualGpuDurationNanos = actualGpuDurationNanos; - } - - /** - * Returns the work period start timestamp based in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getWorkPeriodStartTimestampNanos() { - return mWorkPeriodStartTimestampNanos; - } - - /** - * Returns the actual total duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getActualTotalDurationNanos() { - return mActualTotalDurationNanos; - } - - /** - * Returns the actual CPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getActualCpuDurationNanos() { - return mActualCpuDurationNanos; - } - - /** - * Returns the actual GPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getActualGpuDurationNanos() { - return mActualGpuDurationNanos; - } - - /** - * @hide - */ - public long getTimestampNanos() { - return mTimestampNanos; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeLong(mWorkPeriodStartTimestampNanos); - dest.writeLong(mActualTotalDurationNanos); - dest.writeLong(mActualCpuDurationNanos); - dest.writeLong(mActualGpuDurationNanos); - dest.writeLong(mTimestampNanos); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof WorkDuration)) { - return false; - } - WorkDuration workDuration = (WorkDuration) obj; - return workDuration.mTimestampNanos == this.mTimestampNanos - && workDuration.mWorkPeriodStartTimestampNanos == this.mWorkPeriodStartTimestampNanos - && workDuration.mActualTotalDurationNanos == this.mActualTotalDurationNanos - && workDuration.mActualCpuDurationNanos == this.mActualCpuDurationNanos - && workDuration.mActualGpuDurationNanos == this.mActualGpuDurationNanos; - } - - @Override - public int hashCode() { - return Objects.hash(mWorkPeriodStartTimestampNanos, mActualTotalDurationNanos, - mActualCpuDurationNanos, mActualGpuDurationNanos, mTimestampNanos); - } -} diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index d405d1d0cec6..a78f221fc962 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -61,11 +61,4 @@ flag { namespace: "backstage_power" description: "Guards a new API in PowerManager to check if battery saver is supported or not." bug: "305067031" -} - -flag { - name: "adpf_gpu_report_actual_work_duration" - namespace: "game" - description: "Guards the ADPF GPU APIs." - bug: "284324521" -} +}
\ No newline at end of file diff --git a/core/java/android/permission/IOnPermissionsChangeListener.aidl b/core/java/android/permission/IOnPermissionsChangeListener.aidl index afacf1a74ca6..c68c0c9408a7 100644 --- a/core/java/android/permission/IOnPermissionsChangeListener.aidl +++ b/core/java/android/permission/IOnPermissionsChangeListener.aidl @@ -21,5 +21,5 @@ package android.permission; * {@hide} */ oneway interface IOnPermissionsChangeListener { - void onPermissionsChanged(int uid, String deviceId); + void onPermissionsChanged(int uid, String persistentDeviceId); } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 7a158c548a38..91adc37cb654 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1738,8 +1738,9 @@ public final class PermissionManager { } @Override - public void onPermissionsChanged(int uid, String deviceId) { - mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0, deviceId).sendToTarget(); + public void onPermissionsChanged(int uid, String persistentDeviceId) { + mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0, persistentDeviceId) + .sendToTarget(); } @Override @@ -1747,8 +1748,8 @@ public final class PermissionManager { switch (msg.what) { case MSG_PERMISSIONS_CHANGED: { final int uid = msg.arg1; - final String deviceId = msg.obj.toString(); - mListener.onPermissionsChanged(uid, deviceId); + final String persistentDeviceId = msg.obj.toString(); + mListener.onPermissionsChanged(uid, persistentDeviceId); return true; } default: diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 33c15d775ff1..ff6ec29bb8ac 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -18,6 +18,7 @@ package android.provider; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -36,6 +37,7 @@ import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.Application; import android.app.AutomaticZenRule; +import android.app.Flags; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.SearchManager; @@ -1904,6 +1906,36 @@ public final class Settings { = "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS"; /** + * Activity Action: Shows the settings page for an {@link AutomaticZenRule} mode. + * <p> + * Users can change the behavior of the mode when it's activated and access the owning app's + * additional configuration screen, where triggering criteria can be modified (see + * {@link AutomaticZenRule#setConfigurationActivity(ComponentName)}). + * <p> + * A matching Activity will only be found if + * {@link NotificationManager#areAutomaticZenRulesUserManaged()} is true. + * <p> + * Input: Intent's data URI set with an application name, using the "package" schema (like + * "package:com.my.app"). + * Input: The id of the rule, provided in {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID}. + * <p> + * Output: Nothing. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS + = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS"; + + /** + * Activity Extra: The String id of the {@link AutomaticZenRule mode} settings to display. + * <p> + * This must be passed as an extra field to the {@link #ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID + = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID"; + + /** * Activity Action: Show settings for video captioning. * <p> * In some cases, a matching Activity may not exist, so ensure you safeguard diff --git a/core/java/android/service/notification/OWNERS b/core/java/android/service/notification/OWNERS index bb0e6aba436b..cb0b5fa6a029 100644 --- a/core/java/android/service/notification/OWNERS +++ b/core/java/android/service/notification/OWNERS @@ -2,6 +2,7 @@ juliacr@google.com yurilin@google.com +matiashe@google.com jeffdq@google.com dsandler@android.com dsandler@google.com diff --git a/core/java/android/service/timezone/TEST_MAPPING b/core/java/android/service/timezone/TEST_MAPPING index 21a8eab19837..e5910ea4a1fa 100644 --- a/core/java/android/service/timezone/TEST_MAPPING +++ b/core/java/android/service/timezone/TEST_MAPPING @@ -1,6 +1,5 @@ { - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + "presubmit": [ { "name": "FrameworksTimeCoreTests", "options": [ @@ -8,7 +7,10 @@ "include-filter": "android.service." } ] - }, + } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ { "name": "CtsLocationTimeZoneManagerHostTest" } diff --git a/core/java/android/util/DataUnit.java b/core/java/android/util/DataUnit.java index cc33af32ba93..10905e1b1908 100644 --- a/core/java/android/util/DataUnit.java +++ b/core/java/android/util/DataUnit.java @@ -32,6 +32,7 @@ import java.util.concurrent.TimeUnit; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public enum DataUnit { KILOBYTES { @Override public long toBytes(long v) { return v * 1_000; } }, MEGABYTES { @Override public long toBytes(long v) { return v * 1_000_000; } }, diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java index 4654dbfa9531..d2c5975ea356 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -48,6 +48,9 @@ import java.util.regex.Pattern; * They carry a payload of one or more int, long, or String values. The * event-log-tags file defines the payload contents for each type code. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass +@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( + "com.android.hoststubgen.nativesubstitution.EventLog_host") public class EventLog { /** @hide */ public EventLog() {} @@ -416,6 +419,7 @@ public class EventLog { /** * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done. */ + @android.ravenwood.annotation.RavenwoodReplace private static synchronized void readTagsFile() { if (sTagCodes != null && sTagNames != null) return; @@ -441,8 +445,7 @@ public class EventLog { try { int num = Integer.parseInt(m.group(1)); String name = m.group(2); - sTagCodes.put(name, num); - sTagNames.put(num, name); + registerTagLocked(num, name); } catch (NumberFormatException e) { Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e); } @@ -454,4 +457,20 @@ public class EventLog { try { if (reader != null) reader.close(); } catch (IOException e) {} } } + + private static void registerTagLocked(int num, String name) { + sTagCodes.put(name, num); + sTagNames.put(num, name); + } + + private static synchronized void readTagsFile$ravenwood() { + // TODO: restore parsing logic once we carry into runtime + sTagCodes = new HashMap<String, Integer>(); + sTagNames = new HashMap<Integer, String>(); + + // Hard-code a few common tags + registerTagLocked(524288, "sysui_action"); + registerTagLocked(524290, "sysui_count"); + registerTagLocked(524291, "sysui_histogram"); + } } diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java index c04a71c4d31b..413ae1f5cbcf 100644 --- a/core/java/android/util/IntArray.java +++ b/core/java/android/util/IntArray.java @@ -26,6 +26,7 @@ import java.util.Arrays; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class IntArray implements Cloneable { private static final int MIN_CAPACITY_INCREMENT = 12; diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java index 3101c0da6986..4c7ef08ddfcb 100644 --- a/core/java/android/util/LongArray.java +++ b/core/java/android/util/LongArray.java @@ -30,6 +30,7 @@ import java.util.Arrays; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LongArray implements Cloneable { private static final int MIN_CAPACITY_INCREMENT = 12; diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java index 3aeeccaba250..c0ceb9ea8204 100644 --- a/core/java/android/util/Slog.java +++ b/core/java/android/util/Slog.java @@ -31,6 +31,7 @@ import android.os.Build; * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class Slog { private Slog() { @@ -216,6 +217,7 @@ public final class Slog { * @see Log#wtf(String, String) */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @android.ravenwood.annotation.RavenwoodThrow public static int wtf(@Nullable String tag, @NonNull String msg) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true); } @@ -223,6 +225,7 @@ public final class Slog { /** * Similar to {@link #wtf(String, String)}, but does not output anything to the log. */ + @android.ravenwood.annotation.RavenwoodThrow public static void wtfQuiet(@Nullable String tag, @NonNull String msg) { Log.wtfQuiet(Log.LOG_ID_SYSTEM, tag, msg, true); } @@ -241,6 +244,7 @@ public final class Slog { * @see Log#wtfStack(String, String) */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @android.ravenwood.annotation.RavenwoodThrow public static int wtfStack(@Nullable String tag, @NonNull String msg) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true, true); } @@ -259,6 +263,7 @@ public final class Slog { * * @see Log#wtf(String, Throwable) */ + @android.ravenwood.annotation.RavenwoodThrow public static int wtf(@Nullable String tag, @Nullable Throwable tr) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true); } @@ -279,6 +284,7 @@ public final class Slog { * @see Log#wtf(String, String, Throwable) */ @UnsupportedAppUsage + @android.ravenwood.annotation.RavenwoodThrow public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true); } diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index d06b0ce1a2d8..bff8db13ad14 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -41,6 +41,8 @@ import java.util.List; /** * A class containing utility methods related to time zones. */ +@android.ravenwood.annotation.RavenwoodKeepPartialClass +@android.ravenwood.annotation.RavenwoodKeepStaticInitializer public class TimeUtils { /** @hide */ public TimeUtils() {} /** {@hide} */ @@ -180,6 +182,7 @@ public class TimeUtils { private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10]; private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10]; + @android.ravenwood.annotation.RavenwoodKeep static private int accumField(int amt, int suffix, boolean always, int zeropad) { if (amt > 999) { int num = 0; @@ -202,6 +205,7 @@ public class TimeUtils { return 0; } + @android.ravenwood.annotation.RavenwoodKeep static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos, boolean always, int zeropad) { if (always || amt > 0) { @@ -242,6 +246,7 @@ public class TimeUtils { return pos; } + @android.ravenwood.annotation.RavenwoodKeep private static int formatDurationLocked(long duration, int fieldLen) { if (sFormatStr.length < fieldLen) { sFormatStr = new char[fieldLen]; @@ -314,6 +319,7 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long duration, StringBuilder builder) { synchronized (sFormatSync) { int len = formatDurationLocked(duration, 0); @@ -322,6 +328,7 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long duration, StringBuilder builder, int fieldLen) { synchronized (sFormatSync) { int len = formatDurationLocked(duration, fieldLen); @@ -331,6 +338,7 @@ public class TimeUtils { /** @hide Just for debugging; not internationalized. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long duration, PrintWriter pw, int fieldLen) { synchronized (sFormatSync) { int len = formatDurationLocked(duration, fieldLen); @@ -340,6 +348,7 @@ public class TimeUtils { /** @hide Just for debugging; not internationalized. */ @TestApi + @android.ravenwood.annotation.RavenwoodKeep public static String formatDuration(long duration) { synchronized (sFormatSync) { int len = formatDurationLocked(duration, 0); @@ -349,11 +358,13 @@ public class TimeUtils { /** @hide Just for debugging; not internationalized. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long duration, PrintWriter pw) { formatDuration(duration, pw, 0); } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long time, long now, StringBuilder sb) { if (time == 0) { sb.append("--"); @@ -363,6 +374,7 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long time, long now, PrintWriter pw) { if (time == 0) { pw.print("--"); @@ -372,16 +384,19 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static String formatUptime(long time) { return formatTime(time, SystemClock.uptimeMillis()); } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static String formatRealtime(long time) { return formatTime(time, SystemClock.elapsedRealtime()); } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static String formatTime(long time, long referenceTime) { long diff = time - referenceTime; if (diff > 0) { @@ -402,6 +417,7 @@ public class TimeUtils { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @android.ravenwood.annotation.RavenwoodKeep public static String logTimeOfDay(long millis) { Calendar c = Calendar.getInstance(); if (millis >= 0) { @@ -413,6 +429,7 @@ public class TimeUtils { } /** {@hide} */ + @android.ravenwood.annotation.RavenwoodKeep public static String formatForLogging(long millis) { if (millis <= 0) { return "unknown"; @@ -426,6 +443,7 @@ public class TimeUtils { * * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static void dumpTime(PrintWriter pw, long time) { pw.print(sDumpDateFormat.format(new Date(time))); } @@ -457,6 +475,7 @@ public class TimeUtils { * * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static void dumpTimeWithDelta(PrintWriter pw, long time, long now) { pw.print(sDumpDateFormat.format(new Date(time))); if (time == now) { diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 43bfe139c223..53aed498f110 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -23,7 +23,7 @@ import static java.util.Collections.EMPTY_LIST; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; -import android.annotation.Hide; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -752,6 +752,7 @@ public class AccessibilityNodeInfo implements Parcelable { * {@link #isGranularScrollingSupported()} to check if granular scrolling is supported. * </p> */ + @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING) public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT"; @@ -2608,6 +2609,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return True if all scroll actions that could support * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT} have done so, false otherwise. */ + @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING) public boolean isGranularScrollingSupported() { return getBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING); } @@ -2626,6 +2628,7 @@ public class AccessibilityNodeInfo implements Parcelable { * * @throws IllegalStateException If called from an AccessibilityService. */ + @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING) public void setGranularScrollingSupported(boolean granularScrollingSupported) { setBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING, granularScrollingSupported); @@ -6119,6 +6122,7 @@ public class AccessibilityNodeInfo implements Parcelable { * This should be used for {@code mItemCount} and * {@code mImportantForAccessibilityItemCount} when values for those fields are not known. */ + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public static final int UNDEFINED = -1; private int mRowCount; @@ -6229,8 +6233,8 @@ public class AccessibilityNodeInfo implements Parcelable { * the item count is not known. * @param importantForAccessibilityItemCount The count of the collection's views considered * important for accessibility. + * @hide */ - @Hide public CollectionInfo(int rowCount, int columnCount, boolean hierarchical, int selectionMode, int itemCount, int importantForAccessibilityItemCount) { mRowCount = rowCount; @@ -6287,6 +6291,7 @@ public class AccessibilityNodeInfo implements Parcelable { * * @return The count of items, which may be {@code UNDEFINED} if the count is not known. */ + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public int getItemCount() { return mItemCount; } @@ -6297,6 +6302,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return The count of items important for accessibility, which may be {@code UNDEFINED} * if the count is not known. */ + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public int getImportantForAccessibilityItemCount() { return mImportantForAccessibilityItemCount; } @@ -6323,6 +6329,7 @@ public class AccessibilityNodeInfo implements Parcelable { * The builder for CollectionInfo. */ + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public static final class Builder { private int mRowCount = 0; private int mColumnCount = 0; @@ -6334,6 +6341,7 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Creates a new Builder. */ + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public Builder() { } @@ -6343,6 +6351,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setRowCount(int rowCount) { mRowCount = rowCount; return this; @@ -6354,6 +6363,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setColumnCount(int columnCount) { mColumnCount = columnCount; return this; @@ -6364,6 +6374,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setHierarchical(boolean hierarchical) { mHierarchical = hierarchical; return this; @@ -6375,6 +6386,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setSelectionMode(int selectionMode) { mSelectionMode = selectionMode; return this; @@ -6389,6 +6401,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setItemCount(int itemCount) { mItemCount = itemCount; return this; @@ -6401,6 +6414,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return This builder. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo.Builder setImportantForAccessibilityItemCount( int importantForAccessibilityItemCount) { mImportantForAccessibilityItemCount = importantForAccessibilityItemCount; @@ -6411,6 +6425,7 @@ public class AccessibilityNodeInfo implements Parcelable { * Creates a new {@link CollectionInfo} instance. */ @NonNull + @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS) public CollectionInfo build() { CollectionInfo collectionInfo = new CollectionInfo(mRowCount, mColumnCount, mHierarchical); diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 950fa4b1d711..c337cb45d3a6 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -17,6 +17,13 @@ flag { } flag { + namespace: "accessibility" + name: "collection_info_item_counts" + description: "Fields for total items and the number of important for accessibility items in a collection" + bug: "302376158" +} + +flag { name: "deduplicate_accessibility_warning_dialog" namespace: "accessibility" description: "Removes duplicate definition of the accessibility warning dialog." @@ -39,6 +46,13 @@ flag { flag { namespace: "accessibility" + name: "granular_scrolling" + description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen" + bug: "302376158" +} + +flag { + namespace: "accessibility" name: "update_always_on_a11y_service" description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut." bug: "298869916" diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 42a1f1e58967..0da03fb5aaeb 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -37,5 +37,14 @@ flag { namespace: "window_surfaces" name: "remove_capture_display" description: "Remove uses of ScreenCapture#captureDisplay" + is_fixed_read_only: true bug: "293445881" -}
\ No newline at end of file +} + +flag { + namespace: "window_surfaces" + name: "allow_disable_activity_record_input_sink" + description: "Whether to allow system activity to disable ActivityRecordInputSink" + is_fixed_read_only: true + bug: "262477923" +} diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index b600b22751ff..933cc49f8602 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -9,6 +9,16 @@ flag { bug: "260873529" } +# Using a fixed read only flag because there are ClientTransaction scheduling before +# WindowManagerService creation. +flag { + namespace: "windowing_sdk" + name: "bundle_client_transaction_flag" + description: "To bundle multiple ClientTransactionItems into one ClientTransaction" + bug: "260873529" + is_fixed_read_only: true +} + flag { namespace: "windowing_sdk" name: "activity_embedding_overlay_presentation_flag" diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index df6c1538fc6d..7be27be2e798 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -54,22 +54,6 @@ public class SystemUiSystemPropertiesFlags { */ public static final class NotificationFlags { - /** - * FOR DEVELOPMENT / TESTING ONLY!!! - * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission. - * NOTE: enabling this implies SHOW_STICKY_HUN_FOR_DENIED_FSI in SystemUI - */ - public static final Flag FSI_FORCE_DEMOTE = - devFlag("persist.sysui.notification.fsi_force_demote"); - - /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */ - public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI = - releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi"); - - /** Gating the redaction of OTP notifications on the lockscreen */ - public static final Flag OTP_REDACTION = - devFlag("persist.sysui.notification.otp_redaction"); - /** Gating the logging of DND state change events. */ public static final Flag LOG_DND_STATE_EVENTS = releasedFlag("persist.sysui.notification.log_dnd_state_events"); diff --git a/core/java/com/android/internal/pm/OWNERS b/core/java/com/android/internal/pm/OWNERS new file mode 100644 index 000000000000..6ef34e2e5d4b --- /dev/null +++ b/core/java/com/android/internal/pm/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 36137 + +file:/PACKAGE_MANAGER_OWNERS + diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp index aebe7ea7ee61..95bf49fe501e 100644 --- a/core/jni/android_os_PerformanceHintManager.cpp +++ b/core/jni/android_os_PerformanceHintManager.cpp @@ -16,16 +16,15 @@ #define LOG_TAG "PerfHint-jni" -#include <android/performance_hint.h> +#include "jni.h" + #include <dlfcn.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <utils/Log.h> - #include <vector> #include "core_jni_helpers.h" -#include "jni.h" namespace android { @@ -45,11 +44,6 @@ typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t); typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t); typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const); typedef void (*APH_setPreferPowerEfficiency)(APerformanceHintSession*, bool); -typedef void (*APH_reportActualWorkDuration2)(APerformanceHintSession*, AWorkDuration*); - -typedef AWorkDuration* (*AWD_create)(); -typedef void (*AWD_setTimeNanos)(AWorkDuration*, int64_t); -typedef void (*AWD_release)(AWorkDuration*); bool gAPerformanceHintBindingInitialized = false; APH_getManager gAPH_getManagerFn = nullptr; @@ -62,14 +56,6 @@ APH_sendHint gAPH_sendHintFn = nullptr; APH_setThreads gAPH_setThreadsFn = nullptr; APH_getThreadIds gAPH_getThreadIdsFn = nullptr; APH_setPreferPowerEfficiency gAPH_setPreferPowerEfficiencyFn = nullptr; -APH_reportActualWorkDuration2 gAPH_reportActualWorkDuration2Fn = nullptr; - -AWD_create gAWD_createFn = nullptr; -AWD_setTimeNanos gAWD_setWorkPeriodStartTimestampNanosFn = nullptr; -AWD_setTimeNanos gAWD_setActualTotalDurationNanosFn = nullptr; -AWD_setTimeNanos gAWD_setActualCpuDurationNanosFn = nullptr; -AWD_setTimeNanos gAWD_setActualGpuDurationNanosFn = nullptr; -AWD_release gAWD_releaseFn = nullptr; void ensureAPerformanceHintBindingInitialized() { if (gAPerformanceHintBindingInitialized) return; @@ -126,46 +112,9 @@ void ensureAPerformanceHintBindingInitialized() { (APH_setPreferPowerEfficiency)dlsym(handle_, "APerformanceHint_setPreferPowerEfficiency"); LOG_ALWAYS_FATAL_IF(gAPH_setPreferPowerEfficiencyFn == nullptr, - "Failed to find required symbol " + "Failed to find required symbol" "APerformanceHint_setPreferPowerEfficiency!"); - gAPH_reportActualWorkDuration2Fn = - (APH_reportActualWorkDuration2)dlsym(handle_, - "APerformanceHint_reportActualWorkDuration2"); - LOG_ALWAYS_FATAL_IF(gAPH_reportActualWorkDuration2Fn == nullptr, - "Failed to find required symbol " - "APerformanceHint_reportActualWorkDuration2!"); - - gAWD_createFn = (AWD_create)dlsym(handle_, "AWorkDuration_create"); - LOG_ALWAYS_FATAL_IF(gAWD_createFn == nullptr, - "Failed to find required symbol AWorkDuration_create!"); - - gAWD_setWorkPeriodStartTimestampNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setWorkPeriodStartTimestampNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setWorkPeriodStartTimestampNanosFn == nullptr, - "Failed to find required symbol " - "AWorkDuration_setWorkPeriodStartTimestampNanos!"); - - gAWD_setActualTotalDurationNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualTotalDurationNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setActualTotalDurationNanosFn == nullptr, - "Failed to find required symbol " - "AWorkDuration_setActualTotalDurationNanos!"); - - gAWD_setActualCpuDurationNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualCpuDurationNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setActualCpuDurationNanosFn == nullptr, - "Failed to find required symbol AWorkDuration_setActualCpuDurationNanos!"); - - gAWD_setActualGpuDurationNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualGpuDurationNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setActualGpuDurationNanosFn == nullptr, - "Failed to find required symbol AWorkDuration_setActualGpuDurationNanos!"); - - gAWD_releaseFn = (AWD_release)dlsym(handle_, "AWorkDuration_release"); - LOG_ALWAYS_FATAL_IF(gAWD_releaseFn == nullptr, - "Failed to find required symbol AWorkDuration_release!"); - gAPerformanceHintBindingInitialized = true; } @@ -289,25 +238,6 @@ static void nativeSetPreferPowerEfficiency(JNIEnv* env, jclass clazz, jlong nati enabled); } -static void nativeReportActualWorkDuration2(JNIEnv* env, jclass clazz, jlong nativeSessionPtr, - jlong workPeriodStartTimestampNanos, - jlong actualTotalDurationNanos, - jlong actualCpuDurationNanos, - jlong actualGpuDurationNanos) { - ensureAPerformanceHintBindingInitialized(); - - AWorkDuration* workDuration = gAWD_createFn(); - gAWD_setWorkPeriodStartTimestampNanosFn(workDuration, workPeriodStartTimestampNanos); - gAWD_setActualTotalDurationNanosFn(workDuration, actualTotalDurationNanos); - gAWD_setActualCpuDurationNanosFn(workDuration, actualCpuDurationNanos); - gAWD_setActualGpuDurationNanosFn(workDuration, actualGpuDurationNanos); - - gAPH_reportActualWorkDuration2Fn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), - workDuration); - - gAWD_releaseFn(workDuration); -} - static const JNINativeMethod gPerformanceHintMethods[] = { {"nativeAcquireManager", "()J", (void*)nativeAcquireManager}, {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos}, @@ -319,7 +249,6 @@ static const JNINativeMethod gPerformanceHintMethods[] = { {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads}, {"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds}, {"nativeSetPreferPowerEfficiency", "(JZ)V", (void*)nativeSetPreferPowerEfficiency}, - {"nativeReportActualWorkDuration", "(JJJJJ)V", (void*)nativeReportActualWorkDuration2}, }; int register_android_os_PerformanceHintManager(JNIEnv* env) { diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index 5c1d91ff540e..5b68e8ed1ad8 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -184,11 +184,11 @@ status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t void NativeInputEventReceiver::setFdEvents(int events) { if (mFdEvents != events) { mFdEvents = events; - int fd = mInputConsumer.getChannel()->getFd(); + auto&& fd = mInputConsumer.getChannel()->getFd(); if (events) { - mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr); + mMessageQueue->getLooper()->addFd(fd.get(), 0, events, this, nullptr); } else { - mMessageQueue->getLooper()->removeFd(fd); + mMessageQueue->getLooper()->removeFd(fd.get()); } } } diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp index 833952def02b..6bdf8214a1bf 100644 --- a/core/jni/android_view_InputEventSender.cpp +++ b/core/jni/android_view_InputEventSender.cpp @@ -102,8 +102,8 @@ NativeInputEventSender::~NativeInputEventSender() { } status_t NativeInputEventSender::initialize() { - int receiveFd = mInputPublisher.getChannel()->getFd(); - mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL); + auto&& receiveFd = mInputPublisher.getChannel()->getFd(); + mMessageQueue->getLooper()->addFd(receiveFd.get(), 0, ALOOPER_EVENT_INPUT, this, NULL); return OK; } @@ -112,7 +112,7 @@ void NativeInputEventSender::dispose() { LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender."; } - mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd()); + mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd().get()); } status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7b075e6f4872..4d208c6ed30a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2301,6 +2301,7 @@ <!-- Allows system apps to call methods to register itself as a mDNS offload engine. <p>Not for use by third-party or privileged applications. @SystemApi + @FlaggedApi("com.android.net.flags.register_nsd_offload_engine") @hide This should only be used by system apps. --> <permission android:name="android.permission.REGISTER_NSD_OFFLOAD_ENGINE" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 68bad45b6e00..6cd6eb4b8df9 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5465,6 +5465,7 @@ <item>1,1,1.0,.15,15</item> <item>0,0,0.7,0,1</item> <item>0,0,0.83333,0,1</item> + <item>0,0,1.1667,0,1</item> </string-array> <!-- The integer index of the selected option in config_udfps_touch_detection_options --> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 4b02257978d2..2327b20bada6 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -42,6 +42,7 @@ import android.app.IApplicationThread; import android.app.PictureInPictureParams; import android.app.ResourcesManager; import android.app.servertransaction.ActivityConfigurationChangeItem; +import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; @@ -73,6 +74,7 @@ import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.internal.content.ReferrerIntent; +import com.android.window.flags.Flags; import org.junit.After; import org.junit.Before; @@ -227,7 +229,8 @@ public class ActivityThreadTest { try { // Send process level config change. ClientTransaction transaction = newTransaction(activityThread); - transaction.addCallback(ConfigurationChangeItem.obtain(newConfig, DEVICE_ID_INVALID)); + addClientTransactionItem(transaction, ConfigurationChangeItem.obtain( + newConfig, DEVICE_ID_INVALID)); appThread.scheduleTransaction(transaction); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -243,7 +246,7 @@ public class ActivityThreadTest { newConfig.seq++; newConfig.smallestScreenWidthDp++; transaction = newTransaction(activityThread); - transaction.addCallback(ActivityConfigurationChangeItem.obtain( + addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain( activity.getActivityToken(), newConfig)); appThread.scheduleTransaction(transaction); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -444,16 +447,16 @@ public class ActivityThreadTest { activity.mTestLatch = new CountDownLatch(1); ClientTransaction transaction = newTransaction(activityThread); - transaction.addCallback(ConfigurationChangeItem.obtain( + addClientTransactionItem(transaction, ConfigurationChangeItem.obtain( processConfigLandscape, DEVICE_ID_INVALID)); appThread.scheduleTransaction(transaction); transaction = newTransaction(activityThread); - transaction.addCallback(ActivityConfigurationChangeItem.obtain( + addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain( activity.getActivityToken(), activityConfigLandscape)); - transaction.addCallback(ConfigurationChangeItem.obtain( + addClientTransactionItem(transaction, ConfigurationChangeItem.obtain( processConfigPortrait, DEVICE_ID_INVALID)); - transaction.addCallback(ActivityConfigurationChangeItem.obtain( + addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain( activity.getActivityToken(), activityConfigPortrait)); appThread.scheduleTransaction(transaction); @@ -840,8 +843,8 @@ public class ActivityThreadTest { false /* shouldSendCompatFakeFocus*/); final ClientTransaction transaction = newTransaction(activity); - transaction.addCallback(callbackItem); - transaction.setLifecycleStateRequest(resumeStateRequest); + addClientTransactionItem(transaction, callbackItem); + addClientTransactionItem(transaction, resumeStateRequest); return transaction; } @@ -853,7 +856,7 @@ public class ActivityThreadTest { false /* shouldSendCompatFakeFocus */); final ClientTransaction transaction = newTransaction(activity); - transaction.setLifecycleStateRequest(resumeStateRequest); + addClientTransactionItem(transaction, resumeStateRequest); return transaction; } @@ -864,7 +867,7 @@ public class ActivityThreadTest { activity.getActivityToken(), 0 /* configChanges */); final ClientTransaction transaction = newTransaction(activity); - transaction.setLifecycleStateRequest(stopStateRequest); + addClientTransactionItem(transaction, stopStateRequest); return transaction; } @@ -876,7 +879,7 @@ public class ActivityThreadTest { activity.getActivityToken(), config); final ClientTransaction transaction = newTransaction(activity); - transaction.addCallback(item); + addClientTransactionItem(transaction, item); return transaction; } @@ -888,7 +891,7 @@ public class ActivityThreadTest { resume); final ClientTransaction transaction = newTransaction(activity); - transaction.addCallback(item); + addClientTransactionItem(transaction, item); return transaction; } @@ -903,6 +906,17 @@ public class ActivityThreadTest { return ClientTransaction.obtain(activityThread.getApplicationThread()); } + private static void addClientTransactionItem(@NonNull ClientTransaction transaction, + @NonNull ClientTransactionItem item) { + if (Flags.bundleClientTransactionFlag()) { + transaction.addTransactionItem(item); + } else if (item.isActivityLifecycleItem()) { + transaction.setLifecycleStateRequest((ActivityLifecycleItem) item); + } else { + transaction.addCallback(item); + } + } + // Test activity public static class TestActivity extends Activity { static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter"; diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java index 2ec58d43477d..5a202c5e8834 100644 --- a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java +++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java @@ -20,6 +20,7 @@ import static android.view.Surface.ROTATION_90; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -43,11 +44,29 @@ import java.util.Random; @LargeTest public class ParcelableUsageEventListTest { private static final int SMALL_TEST_EVENT_COUNT = 100; - private static final int LARGE_TEST_EVENT_COUNT = 10000; + private static final int LARGE_TEST_EVENT_COUNT = 30000; private Random mRandom = new Random(); @Test + public void testNullList() throws Exception { + Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(new ParcelableUsageEventList(null), 0); + fail("Expected IllegalArgumentException with null list."); + } catch (IllegalArgumentException expected) { + // Expected. + } finally { + parcel.recycle(); + } + } + + @Test + public void testEmptyList() throws Exception { + testParcelableUsageEventList(0); + } + + @Test public void testSmallList() throws Exception { testParcelableUsageEventList(SMALL_TEST_EVENT_COUNT); } @@ -58,15 +77,15 @@ public class ParcelableUsageEventListTest { } private void testParcelableUsageEventList(int eventCount) throws Exception { - List<Event> smallList = new ArrayList<>(); + List<Event> eventList = new ArrayList<>(); for (int i = 0; i < eventCount; i++) { - smallList.add(generateUsageEvent()); + eventList.add(generateUsageEvent()); } ParcelableUsageEventList slice; Parcel parcel = Parcel.obtain(); try { - parcel.writeParcelable(new ParcelableUsageEventList(smallList), 0); + parcel.writeParcelable(new ParcelableUsageEventList(eventList), 0); parcel.setDataPosition(0); slice = parcel.readParcelable(getClass().getClassLoader(), ParcelableUsageEventList.class); @@ -79,7 +98,7 @@ public class ParcelableUsageEventListTest { assertEquals(eventCount, slice.getList().size()); for (int i = 0; i < eventCount; i++) { - compareUsageEvent(smallList.get(i), slice.getList().get(i)); + compareUsageEvent(eventList.get(i), slice.getList().get(i)); } } diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java index 9b4dec4118a1..20ba4270e6fc 100644 --- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java +++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java @@ -182,42 +182,4 @@ public class PerformanceHintManagerTest { s.setPreferPowerEfficiency(true); s.setPreferPowerEfficiency(true); } - - @Test - public void testReportActualWorkDurationWithWorkDurationClass() { - Session s = createSession(); - assumeNotNull(s); - s.updateTargetWorkDuration(16); - s.reportActualWorkDuration(new WorkDuration(1, 12, 8, 6)); - s.reportActualWorkDuration(new WorkDuration(1, 33, 14, 20)); - s.reportActualWorkDuration(new WorkDuration(1, 14, 10, 6)); - } - - @Test - public void testReportActualWorkDurationWithWorkDurationClass_IllegalArgument() { - Session s = createSession(); - assumeNotNull(s); - s.updateTargetWorkDuration(16); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1)); - }); - } } diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp index 580e73c331a1..06340a222ac6 100644 --- a/core/tests/utiltests/Android.bp +++ b/core/tests/utiltests/Android.bp @@ -35,6 +35,7 @@ android_test { "androidx.test.ext.junit", "truth", "servicestests-utils", + "ravenwood-junit", ], libs: [ @@ -50,3 +51,22 @@ android_test { test_suites: ["device-tests"], } + +android_ravenwood_test { + name: "FrameworksUtilTestsRavenwood", + static_libs: [ + "androidx.annotation_annotation", + "androidx.test.rules", + ], + srcs: [ + "src/android/util/DataUnitTest.java", + "src/android/util/EventLogTest.java", + "src/android/util/IndentingPrintWriterTest.java", + "src/android/util/IntArrayTest.java", + "src/android/util/LocalLogTest.java", + "src/android/util/LongArrayTest.java", + "src/android/util/SlogTest.java", + "src/android/util/TimeUtilsTest.java", + ], + auto_gen_config: true, +} diff --git a/core/tests/coretests/src/android/util/DataUnitTest.java b/core/tests/utiltests/src/android/util/DataUnitTest.java index 034cbddc0144..af9ebc833d4b 100644 --- a/core/tests/coretests/src/android/util/DataUnitTest.java +++ b/core/tests/utiltests/src/android/util/DataUnitTest.java @@ -16,12 +16,18 @@ package android.util; +import static org.junit.Assert.assertEquals; + import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; @SmallTest -public class DataUnitTest extends TestCase { +@RunWith(AndroidJUnit4.class) +public class DataUnitTest { + @Test public void testSi() throws Exception { assertEquals(12_000L, DataUnit.KILOBYTES.toBytes(12)); assertEquals(12_000_000L, DataUnit.MEGABYTES.toBytes(12)); @@ -29,6 +35,7 @@ public class DataUnitTest extends TestCase { assertEquals(12_000_000_000_000L, DataUnit.TERABYTES.toBytes(12)); } + @Test public void testIec() throws Exception { assertEquals(12_288L, DataUnit.KIBIBYTES.toBytes(12)); assertEquals(12_582_912L, DataUnit.MEBIBYTES.toBytes(12)); diff --git a/core/tests/coretests/src/android/util/EventLogTest.java b/core/tests/utiltests/src/android/util/EventLogTest.java index 94e72c4a8d52..0ebf2c163d19 100644 --- a/core/tests/coretests/src/android/util/EventLogTest.java +++ b/core/tests/utiltests/src/android/util/EventLogTest.java @@ -16,23 +16,42 @@ package android.util; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.EventLog.Event; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import junit.framework.AssertionFailedError; +import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; /** Unit tests for {@link android.util.EventLog} */ +@SmallTest +@RunWith(AndroidJUnit4.class) public class EventLogTest { + @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Test + public void testSimple() throws Throwable { + EventLog.writeEvent(42, 42); + EventLog.writeEvent(42, 42L); + EventLog.writeEvent(42, 42f); + EventLog.writeEvent(42, "forty-two"); + EventLog.writeEvent(42, 42, "forty-two", null, 42); + } @Test + @IgnoreUnderRavenwood(reason = "Reading not yet supported") public void testWithNewData() throws Throwable { Event event = createEvent(() -> { EventLog.writeEvent(314, 123); diff --git a/core/tests/coretests/src/android/util/LocalLogTest.java b/core/tests/utiltests/src/android/util/LocalLogTest.java index d4861cd391a8..5025a3d0980c 100644 --- a/core/tests/coretests/src/android/util/LocalLogTest.java +++ b/core/tests/utiltests/src/android/util/LocalLogTest.java @@ -16,9 +16,13 @@ package android.util; +import static org.junit.Assert.assertTrue; + import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; import java.io.PrintWriter; import java.io.StringWriter; @@ -27,13 +31,16 @@ import java.util.Collections; import java.util.List; @LargeTest -public class LocalLogTest extends TestCase { +@RunWith(AndroidJUnit4.class) +public class LocalLogTest { + @Test public void testA_localTimestamps() { boolean localTimestamps = true; doTestA(localTimestamps); } + @Test public void testA_nonLocalTimestamps() { boolean localTimestamps = false; doTestA(localTimestamps); @@ -49,6 +56,7 @@ public class LocalLogTest extends TestCase { testcase(new LocalLog(10, localTimestamps), lines, want); } + @Test public void testB() { String[] lines = { "foo", @@ -59,6 +67,7 @@ public class LocalLogTest extends TestCase { testcase(new LocalLog(0), lines, want); } + @Test public void testC() { String[] lines = { "dropped", diff --git a/core/tests/utiltests/src/android/util/SlogTest.java b/core/tests/utiltests/src/android/util/SlogTest.java new file mode 100644 index 000000000000..6f761e348dbe --- /dev/null +++ b/core/tests/utiltests/src/android/util/SlogTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.util; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class SlogTest { + private static final String TAG = "tag"; + private static final String MSG = "msg"; + private static final Throwable THROWABLE = new Throwable(); + + @Test + public void testSimple() { + Slog.v(TAG, MSG); + Slog.d(TAG, MSG); + Slog.i(TAG, MSG); + Slog.w(TAG, MSG); + Slog.e(TAG, MSG); + } + + @Test + public void testThrowable() { + Slog.v(TAG, MSG, THROWABLE); + Slog.d(TAG, MSG, THROWABLE); + Slog.i(TAG, MSG, THROWABLE); + Slog.w(TAG, MSG, THROWABLE); + Slog.e(TAG, MSG, THROWABLE); + } +} diff --git a/core/tests/utiltests/src/android/util/TimeUtilsTest.java b/core/tests/utiltests/src/android/util/TimeUtilsTest.java new file mode 100644 index 000000000000..e8246c83e086 --- /dev/null +++ b/core/tests/utiltests/src/android/util/TimeUtilsTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import static org.junit.Assert.assertEquals; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.function.Consumer; + +@RunWith(AndroidJUnit4.class) +public class TimeUtilsTest { + public static final long SECOND_IN_MILLIS = 1000; + public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; + public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; + public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; + public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7; + + @Test + public void testFormatTime() { + assertEquals("1672556400000 (now)", + TimeUtils.formatTime(1672556400000L, 1672556400000L)); + assertEquals("1672556400000 (in 10 ms)", + TimeUtils.formatTime(1672556400000L, 1672556400000L - 10)); + assertEquals("1672556400000 (10 ms ago)", + TimeUtils.formatTime(1672556400000L, 1672556400000L + 10)); + + // Uses formatter above, so we just care that it doesn't crash + TimeUtils.formatRealtime(1672556400000L); + TimeUtils.formatUptime(1672556400000L); + } + + @Test + public void testFormatDuration_Zero() { + assertEquals("0", TimeUtils.formatDuration(0)); + } + + @Test + public void testFormatDuration_Negative() { + assertEquals("-10ms", TimeUtils.formatDuration(-10)); + } + + @Test + public void testFormatDuration() { + long accum = 900; + assertEquals("+900ms", TimeUtils.formatDuration(accum)); + + accum += 59 * SECOND_IN_MILLIS; + assertEquals("+59s900ms", TimeUtils.formatDuration(accum)); + + accum += 59 * MINUTE_IN_MILLIS; + assertEquals("+59m59s900ms", TimeUtils.formatDuration(accum)); + + accum += 23 * HOUR_IN_MILLIS; + assertEquals("+23h59m59s900ms", TimeUtils.formatDuration(accum)); + + accum += 6 * DAY_IN_MILLIS; + assertEquals("+6d23h59m59s900ms", TimeUtils.formatDuration(accum)); + } + + @Test + public void testDumpTime() { + assertEquals("2023-01-01 00:00:00.000", runWithPrintWriter((pw) -> { + TimeUtils.dumpTime(pw, 1672556400000L); + })); + assertEquals("2023-01-01 00:00:00.000 (now)", runWithPrintWriter((pw) -> { + TimeUtils.dumpTimeWithDelta(pw, 1672556400000L, 1672556400000L); + })); + assertEquals("2023-01-01 00:00:00.000 (-10ms)", runWithPrintWriter((pw) -> { + TimeUtils.dumpTimeWithDelta(pw, 1672556400000L, 1672556400000L + 10); + })); + } + + @Test + public void testFormatForLogging() { + assertEquals("unknown", TimeUtils.formatForLogging(0)); + assertEquals("unknown", TimeUtils.formatForLogging(-1)); + assertEquals("unknown", TimeUtils.formatForLogging(Long.MIN_VALUE)); + assertEquals("2023-01-01 00:00:00", TimeUtils.formatForLogging(1672556400000L)); + } + + @Test + public void testLogTimeOfDay() { + assertEquals("01-01 00:00:00.000", TimeUtils.logTimeOfDay(1672556400000L)); + } + + public static String runWithPrintWriter(Consumer<PrintWriter> consumer) { + final StringWriter sw = new StringWriter(); + consumer.accept(new PrintWriter(sw)); + return sw.toString(); + } + + public static String runWithStringBuilder(Consumer<StringBuilder> consumer) { + final StringBuilder sb = new StringBuilder(); + consumer.accept(sb); + return sb.toString(); + } +} diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java index 07f1d4a09006..8dc9579e6b52 100644 --- a/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java +++ b/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java @@ -63,7 +63,7 @@ public class HideInCommentsChecker extends BugChecker implements @Override public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) { - final Map<Integer, Tree> javadocableTrees = findJavadocableTrees(tree); + final Map<Integer, Tree> javadocableTrees = findJavadocableTrees(tree, state); final String sourceCode = state.getSourceCode().toString(); for (ErrorProneToken token : ErrorProneTokens.getTokens(sourceCode, state.context)) { for (Tokens.Comment comment : token.comments()) { @@ -112,9 +112,9 @@ public class HideInCommentsChecker extends BugChecker implements } - private Map<Integer, Tree> findJavadocableTrees(CompilationUnitTree tree) { + private Map<Integer, Tree> findJavadocableTrees(CompilationUnitTree tree, VisitorState state) { Map<Integer, Tree> javadoccableTrees = new HashMap<>(); - new SuppressibleTreePathScanner<Void, Void>() { + new SuppressibleTreePathScanner<Void, Void>(state) { @Override public Void visitClass(ClassTree classTree, Void unused) { javadoccableTrees.put(getStartPosition(classTree), classTree); diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index 2ec4524e1241..d659ddd75f72 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -402,8 +402,8 @@ public class BaseRecordingCanvas extends Canvas { } @Override - public final void drawDoubleRoundRect(@NonNull RectF outer, float[] outerRadii, - @NonNull RectF inner, float[] innerRadii, @NonNull Paint paint) { + public final void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii, + @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) { nDrawDoubleRoundRect(mNativeCanvasWrapper, outer.left, outer.top, outer.right, outer.bottom, outerRadii, inner.left, inner.top, inner.right, inner.bottom, innerRadii, diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index c366ccd235db..4d2d960822d1 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -42,3 +42,11 @@ flag { description: "Enables PiP UI state callback on entering" bug: "303718131" } + +flag { + name: "enable_pip2_implementation" + namespace: "multitasking" + description: "Enables the new implementation of PiP (PiP2)" + bug: "290220798" + is_fixed_read_only: true +} diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/libs/WindowManager/Shell/res/values/config_tv.xml index 141be0762ae9..3da5539c9ae6 100644 --- a/packages/SystemUI/communal/layout/AndroidManifest.xml +++ b/libs/WindowManager/Shell/res/values/config_tv.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> - -<!-- Copyright (C) 2023 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. @@ -14,5 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - -<manifest package="com.android.systemui.communal.layout" /> +<resources> + <integer name="config_tvPipEnterFadeOutDuration">500</integer> + <integer name="config_tvPipEnterFadeInDuration">1500</integer> + <integer name="config_tvPipExitFadeOutDuration">500</integer> + <integer name="config_tvPipExitFadeInDuration">500</integer> +</resources>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index c7ab6aa3934e..f5b877a70b84 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -963,7 +963,11 @@ public class BubbleExpandedView extends LinearLayout { && mTaskView.isAttachedToWindow()) { // post this to the looper, because if the device orientation just changed, we need to // let the current shell transition complete before updating the task view bounds. - post(() -> mTaskView.onLocationChanged()); + post(() -> { + if (mTaskView != null) { + mTaskView.onLocationChanged(); + } + }); } if (mIsOverflow) { // post this to the looper so that the view has a chance to be laid out before it can diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index d520ff791e07..8b6c7b663f82 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -258,7 +258,7 @@ public class PipBoundsState { ActivityTaskManager.getService().onPictureInPictureStateChanged( new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */) ); - } catch (RemoteException e) { + } catch (RemoteException | IllegalStateException e) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Unable to set alert PiP state change.", TAG); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index 108aa8275009..1e30d8feb132 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -21,13 +21,13 @@ import android.app.WindowConfiguration import android.content.ComponentName import android.content.Context import android.os.RemoteException -import android.os.SystemProperties import android.util.DisplayMetrics import android.util.Log import android.util.Pair import android.util.TypedValue import android.window.TaskSnapshot import com.android.internal.protolog.common.ProtoLog +import com.android.wm.shell.Flags import com.android.wm.shell.protolog.ShellProtoLogGroup import kotlin.math.abs @@ -37,7 +37,6 @@ object PipUtils { // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. private const val EPSILON = 1e-7 - private const val ENABLE_PIP2_IMPLEMENTATION = "persist.wm.debug.enable_pip2_implementation" /** * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. @@ -138,5 +137,5 @@ object PipUtils { @JvmStatic val isPip2ExperimentEnabled: Boolean - get() = SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false) + get() = Flags.enablePip2Implementation() }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 27dc870e81ae..b158f88a68c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -250,10 +250,10 @@ public abstract class WMShellBaseModule { SyncTransactionQueue syncQueue, @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy, - DockStateReader dockStateReader, - CompatUIConfiguration compatUIConfiguration, - CompatUIShellCommandHandler compatUIShellCommandHandler, - AccessibilityManager accessibilityManager) { + Lazy<DockStateReader> dockStateReader, + Lazy<CompatUIConfiguration> compatUIConfiguration, + Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler, + Lazy<AccessibilityManager> accessibilityManager) { if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) { return Optional.empty(); } @@ -268,10 +268,10 @@ public abstract class WMShellBaseModule { syncQueue, mainExecutor, transitionsLazy, - dockStateReader, - compatUIConfiguration, - compatUIShellCommandHandler, - accessibilityManager)); + dockStateReader.get(), + compatUIConfiguration.get(), + compatUIShellCommandHandler.get(), + accessibilityManager.get())); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java index a9675f976fa9..1947097c2f15 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java @@ -20,6 +20,8 @@ import android.content.Context; import android.os.Handler; import android.os.SystemClock; +import androidx.annotation.NonNull; + import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; @@ -41,7 +43,6 @@ import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; -import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm; import com.android.wm.shell.pip.tv.TvPipBoundsController; @@ -78,11 +79,12 @@ public abstract class TvPipModule { PipDisplayLayoutState pipDisplayLayoutState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, + PipTransitionState pipTransitionState, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, TvPipMenuController tvPipMenuController, PipMediaController pipMediaController, - PipTransitionController pipTransitionController, + TvPipTransition tvPipTransition, TvPipNotificationController tvPipNotificationController, TaskStackListenerImpl taskStackListener, PipParamsChangedForwarder pipParamsChangedForwarder, @@ -99,9 +101,10 @@ public abstract class TvPipModule { pipDisplayLayoutState, tvPipBoundsAlgorithm, tvPipBoundsController, + pipTransitionState, pipAppOpsListener, pipTaskOrganizer, - pipTransitionController, + tvPipTransition, tvPipMenuController, pipMediaController, tvPipNotificationController, @@ -151,25 +154,23 @@ public abstract class TvPipModule { return new LegacySizeSpecSource(context, pipDisplayLayoutState); } - // Handler needed for loadDrawableAsync() in PipControlsViewController @WMSingleton @Provides - static PipTransitionController provideTvPipTransition( + static TvPipTransition provideTvPipTransition( Context context, - ShellInit shellInit, - ShellTaskOrganizer shellTaskOrganizer, - Transitions transitions, + @NonNull ShellInit shellInit, + @NonNull ShellTaskOrganizer shellTaskOrganizer, + @NonNull Transitions transitions, TvPipBoundsState tvPipBoundsState, - PipDisplayLayoutState pipDisplayLayoutState, - PipTransitionState pipTransitionState, - TvPipMenuController pipMenuController, + TvPipMenuController tvPipMenuController, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + PipTransitionState pipTransitionState, PipAnimationController pipAnimationController, - PipSurfaceTransactionHelper pipSurfaceTransactionHelper) { + PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + PipDisplayLayoutState pipDisplayLayoutState) { return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions, - tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController, - tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, - Optional.empty()); + tvPipBoundsState, tvPipMenuController, tvPipBoundsAlgorithm, pipTransitionState, + pipAnimationController, pipSurfaceTransactionHelper, pipDisplayLayoutState); } @WMSingleton @@ -207,7 +208,7 @@ public abstract class TvPipModule { PipTransitionState pipTransitionState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, PipAnimationController pipAnimationController, - PipTransitionController pipTransitionController, + TvPipTransition tvPipTransition, PipParamsChangedForwarder pipParamsChangedForwarder, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<SplitScreenController> splitScreenControllerOptional, @@ -217,7 +218,7 @@ public abstract class TvPipModule { return new TvPipTaskOrganizer(context, syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState, tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController, - pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, + pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder, splitScreenControllerOptional, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index cbed4b5a501f..a58d94ecd19b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -81,15 +81,35 @@ public class PipSurfaceTransactionHelper { */ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds) { + mTmpDestinationRectF.set(destinationBounds); + return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */); + } + + /** + * Operates the scale (setMatrix) on a given transaction and leash + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, RectF destinationBounds) { return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */); } /** - * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation. + * Operates the scale (setMatrix) on a given transaction and leash * @return same {@link PipSurfaceTransactionHelper} instance for method chaining */ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees) { + mTmpDestinationRectF.set(destinationBounds); + return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees); + } + + /** + * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation. + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, RectF destinationBounds, float degrees) { mTmpSourceRectF.set(sourceBounds); // We want the matrix to position the surface relative to the screen coordinates so offset // the source to 0,0 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index c05601b3f04f..b4067d0db112 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -297,9 +297,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // changed RunningTaskInfo when it finishes. private ActivityManager.RunningTaskInfo mDeferredTaskInfo; private WindowContainerToken mToken; - private SurfaceControl mLeash; + protected SurfaceControl mLeash; protected PipTransitionState mPipTransitionState; - private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + protected PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; protected PictureInPictureParams mPictureInPictureParams; private IntConsumer mOnDisplayIdChangeCallback; @@ -973,7 +973,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - cancelCurrentAnimator(); + cancelAnimationOnTaskVanished(); onExitPipFinished(info); if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -981,6 +981,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } } + protected void cancelAnimationOnTaskVanished() { + cancelCurrentAnimator(); + } + @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken"); @@ -1100,7 +1104,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** Called when exiting PIP transition is finished to do the state cleanup. */ - void onExitPipFinished(TaskInfo info) { + public void onExitPipFinished(TaskInfo info) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "onExitPipFinished: %s, state=%s leash=%s", info.topActivity, mPipTransitionState, mLeash); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index a48e969fde35..72c0cd71f198 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -44,6 +44,7 @@ import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import java.util.Collections; import java.util.Set; /** @@ -101,12 +102,29 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0 && !mTvPipBoundsState.isTvPipManuallyCollapsed(); if (isPipExpanded) { - updateGravityOnExpansionToggled(/* expanding= */ true); + updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded); } mTvPipBoundsState.setTvPipExpanded(isPipExpanded); return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds()); } + @Override + public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: getEntryDestinationBoundsIgnoringKeepClearAreas()", TAG); + + updateExpandedPipSize(); + final boolean isPipExpanded = mTvPipBoundsState.isTvExpandedPipSupported() + && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0 + && !mTvPipBoundsState.isTvPipManuallyCollapsed(); + if (isPipExpanded) { + updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded); + } + mTvPipBoundsState.setTvPipExpanded(isPipExpanded); + return adjustBoundsForTemporaryDecor(getTvPipPlacement(Collections.emptySet(), + Collections.emptySet()).getUnstashedBounds()); + } + /** Returns the current bounds adjusted to the new aspect ratio, if valid. */ @Override public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) { @@ -133,16 +151,25 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { */ @NonNull public Placement getTvPipPlacement() { + final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas(); + final Set<Rect> unrestrictedKeepClearAreas = + mTvPipBoundsState.getUnrestrictedKeepClearAreas(); + + return getTvPipPlacement(restrictedKeepClearAreas, unrestrictedKeepClearAreas); + } + + /** + * Calculates the PiP bounds. + */ + @NonNull + private Placement getTvPipPlacement(Set<Rect> restrictedKeepClearAreas, + Set<Rect> unrestrictedKeepClearAreas) { final Size pipSize = getPipSize(); final Rect displayBounds = mTvPipBoundsState.getDisplayBounds(); final Size screenSize = new Size(displayBounds.width(), displayBounds.height()); final Rect insetBounds = new Rect(); getInsetBounds(insetBounds); - final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas(); - final Set<Rect> unrestrictedKeepClearAreas = - mTvPipBoundsState.getUnrestrictedKeepClearAreas(); - mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity()); mKeepClearAlgorithm.setScreenSize(screenSize); mKeepClearAlgorithm.setMovementBounds(insetBounds); @@ -189,8 +216,11 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { int updatedGravity; if (expanding) { - // Save collapsed gravity. - mTvPipBoundsState.setTvPipPreviousCollapsedGravity(mTvPipBoundsState.getTvPipGravity()); + if (!mTvPipBoundsState.isTvPipExpanded()) { + // Save collapsed gravity. + mTvPipBoundsState.setTvPipPreviousCollapsedGravity( + mTvPipBoundsState.getTvPipGravity()); + } if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) { updatedGravity = Gravity.CENTER_HORIZONTAL | currentY; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java index 2b3a93e3c3e8..5ee3734e371d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java @@ -131,6 +131,7 @@ public class TvPipBoundsState extends PipBoundsState { mTvFixedPipOrientation = ORIENTATION_UNDETERMINED; mTvPipGravity = mDefaultGravity; mPreviousCollapsedGravity = mDefaultGravity; + mIsTvPipExpanded = false; mTvPipManuallyCollapsed = false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 72115fdefa05..cd3d38b6500c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -56,6 +56,7 @@ import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellController; @@ -122,6 +123,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final PipDisplayLayoutState mPipDisplayLayoutState; private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; private final TvPipBoundsController mTvPipBoundsController; + private final PipTransitionState mPipTransitionState; private final PipAppOpsListener mAppOpsListener; private final PipTaskOrganizer mPipTaskOrganizer; private final PipMediaController mPipMediaController; @@ -157,6 +159,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipDisplayLayoutState pipDisplayLayoutState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, + PipTransitionState pipTransitionState, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, @@ -177,6 +180,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal pipDisplayLayoutState, tvPipBoundsAlgorithm, tvPipBoundsController, + pipTransitionState, pipAppOpsListener, pipTaskOrganizer, pipTransitionController, @@ -199,6 +203,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipDisplayLayoutState pipDisplayLayoutState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, + PipTransitionState pipTransitionState, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, @@ -212,6 +217,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal Handler mainHandler, ShellExecutor mainExecutor) { mContext = context; + mPipTransitionState = pipTransitionState; mMainHandler = mainHandler; mMainExecutor = mainExecutor; mShellController = shellController; @@ -365,7 +371,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState)); mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */); - onPipDisappeared(); } private void togglePipExpansion() { @@ -420,6 +425,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal @Override public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) { + if (!mPipTransitionState.hasEnteredPip()) { + // Do not schedule a move animation while we're still transitioning into/out of PiP + return; + } + mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds, animationDuration, null); mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds); @@ -447,7 +457,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal return; } mPipTaskOrganizer.removePip(); - onPipDisappeared(); + mTvPipMenuController.closeMenu(); } @Override @@ -477,7 +487,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mPipNotificationController.dismiss(); mActionBroadcastReceiver.unregister(); - mTvPipMenuController.closeMenu(); + mTvPipMenuController.detach(); mTvPipActionsProvider.reset(); mTvPipBoundsState.resetTvPipState(); mTvPipBoundsController.reset(); @@ -501,8 +511,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public void onPipTransitionCanceled(int direction) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState)); - mTvPipMenuController.onPipTransitionFinished( - PipAnimationController.isInPipDirection(direction)); mTvPipActionsProvider.updatePipExpansionState(mTvPipBoundsState.isTvPipExpanded()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index ee55211a73a9..c6803f7beebd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -262,8 +262,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void detach() { - closeMenu(); detachPipMenu(); + switchToMenuMode(MODE_NO_MENU); mLeash = null; } @@ -320,10 +320,21 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx, Rect pipBounds, float alpha) { + movePipMenu(pipTx, pipBounds, alpha); + } + + /** + * Move the PiP menu with the given bounds and update its opacity. + * The PiP SurfaceControl is given if there is a need to synchronize the movements + * on the same frame as PiP. + */ + public void movePipMenu(@Nullable SurfaceControl.Transaction pipTx, @Nullable Rect pipBounds, + float alpha) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha); + "%s: movePipMenu: %s, alpha %s", TAG, + pipBounds != null ? pipBounds.toShortString() : null, alpha); - if (pipBounds.isEmpty()) { + if ((pipBounds == null || pipBounds.isEmpty()) && alpha == ALPHA_NO_CHANGE) { if (pipTx == null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: no transaction given", TAG); @@ -334,28 +345,36 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis return; } - final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView); - final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView); - final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds); if (pipTx == null) { pipTx = new SurfaceControl.Transaction(); } - pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top); - pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top); + + final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView); + final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView); + + if (pipBounds != null) { + final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds); + pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top); + pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top); + updateMenuBounds(pipBounds); + } if (alpha != ALPHA_NO_CHANGE) { pipTx.setAlpha(frontSurface, alpha); pipTx.setAlpha(backSurface, alpha); } - // Synchronize drawing the content in the front and back surfaces together with the pip - // transaction and the position change for the front and back surfaces - final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip"); - syncGroup.add(mPipMenuView.getRootSurfaceControl(), null); - syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null); - updateMenuBounds(pipBounds); - syncGroup.addTransaction(pipTx); - syncGroup.markSyncReady(); + if (pipBounds != null) { + // Synchronize drawing the content in the front and back surfaces together with the pip + // transaction and the position change for the front and back surfaces + final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip"); + syncGroup.add(mPipMenuView.getRootSurfaceControl(), null); + syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null); + syncGroup.addTransaction(pipTx); + syncGroup.markSyncReady(); + } else { + pipTx.apply(); + } } private boolean isMenuAttached() { @@ -388,14 +407,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis final Rect menuBounds = calculateMenuSurfaceBounds(pipBounds); ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString()); - mSystemWindows.updateViewLayout(mPipBackgroundView, - getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(), - menuBounds.height())); - mSystemWindows.updateViewLayout(mPipMenuView, - getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(), - menuBounds.height())); - if (mPipMenuView != null) { - mPipMenuView.setPipBounds(pipBounds); + + boolean needsRelayout = mPipBackgroundView.getLayoutParams().width != menuBounds.width() + || mPipBackgroundView.getLayoutParams().height != menuBounds.height(); + if (needsRelayout) { + mSystemWindows.updateViewLayout(mPipBackgroundView, + getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(), + menuBounds.height())); + mSystemWindows.updateViewLayout(mPipMenuView, + getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(), + menuBounds.height())); + if (mPipMenuView != null) { + mPipMenuView.setPipBounds(pipBounds); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java index f86f987039ba..202d36f0dfbd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java @@ -168,6 +168,9 @@ class TvPipMenuEduTextDrawer extends FrameLayout { * that the edu text will be marqueed */ private boolean isEduTextMarqueed() { + if (mEduTextView.getLayout() == null) { + return false; + } final int availableWidth = (int) mEduTextView.getWidth() - mEduTextView.getCompoundPaddingLeft() - mEduTextView.getCompoundPaddingRight(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java index f315afba9a03..21223c9ac362 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java @@ -35,7 +35,6 @@ import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; -import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -46,6 +45,7 @@ import java.util.Optional; * TV specific changes to the PipTaskOrganizer. */ public class TvPipTaskOrganizer extends PipTaskOrganizer { + private final TvPipTransition mTvPipTransition; public TvPipTaskOrganizer(Context context, @NonNull SyncTransactionQueue syncTransactionQueue, @@ -56,7 +56,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { @NonNull PipMenuController pipMenuController, @NonNull PipAnimationController pipAnimationController, @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper, - @NonNull PipTransitionController pipTransitionController, + @NonNull TvPipTransition tvPipTransition, @NonNull PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenOptional, @NonNull DisplayController displayController, @@ -65,9 +65,10 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { ShellExecutor mainExecutor) { super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController, - surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, + surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder, splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); + mTvPipTransition = tvPipTransition; } @Override @@ -105,4 +106,14 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { // when the menu alpha is 0 (e.g. when a fade-in animation starts). return true; } + + @Override + protected void cancelAnimationOnTaskVanished() { + mTvPipTransition.cancelAnimations(); + if (mLeash != null) { + mSurfaceControlTransactionFactory.getTransaction() + .setAlpha(mLeash, 0f) + .apply(); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java index f24b2b385cad..571c839adf11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java @@ -16,43 +16,822 @@ package com.android.wm.shell.pip.tv; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_PIP; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.transitTypeToString; + +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; +import static com.android.wm.shell.pip.PipMenuController.ALPHA_NO_CHANGE; +import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP; +import static com.android.wm.shell.pip.PipTransitionState.ENTERING_PIP; +import static com.android.wm.shell.pip.PipTransitionState.EXITING_PIP; +import static com.android.wm.shell.pip.PipTransitionState.UNDEFINED; +import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; + +import android.animation.AnimationHandler; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.TaskInfo; import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.IBinder; +import android.os.Trace; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; +import androidx.annotation.FloatRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; -import com.android.wm.shell.pip.PipTransition; +import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; -import java.util.Optional; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; /** * PiP Transition for TV. */ -public class TvPipTransition extends PipTransition { +public class TvPipTransition extends PipTransitionController { + private static final String TAG = "TvPipTransition"; + private static final float ZOOM_ANIMATION_SCALE_FACTOR = 0.97f; + + private final PipTransitionState mPipTransitionState; + private final PipAnimationController mPipAnimationController; + private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; + private final TvPipMenuController mTvPipMenuController; + private final PipDisplayLayoutState mPipDisplayLayoutState; + private final PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory + mTransactionFactory; + + private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = + ThreadLocal.withInitial(() -> { + AnimationHandler handler = new AnimationHandler(); + handler.setProvider(new SfVsyncFrameCallbackProvider()); + return handler; + }); + + private final long mEnterFadeOutDuration; + private final long mEnterFadeInDuration; + private final long mExitFadeOutDuration; + private final long mExitFadeInDuration; + + @Nullable + private Animator mCurrentAnimator; + + /** + * The Task window that is currently in PIP windowing mode. + */ + @Nullable + private WindowContainerToken mCurrentPipTaskToken; + + @Nullable + private IBinder mPendingExitTransition; public TvPipTransition(Context context, @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, TvPipBoundsState tvPipBoundsState, - PipDisplayLayoutState pipDisplayLayoutState, - PipTransitionState pipTransitionState, TvPipMenuController tvPipMenuController, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + PipTransitionState pipTransitionState, PipAnimationController pipAnimationController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, - Optional<SplitScreenController> splitScreenOptional) { - super(context, shellInit, shellTaskOrganizer, transitions, tvPipBoundsState, - pipDisplayLayoutState, pipTransitionState, tvPipMenuController, - tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, - splitScreenOptional); + PipDisplayLayoutState pipDisplayLayoutState) { + super(shellInit, shellTaskOrganizer, transitions, tvPipBoundsState, tvPipMenuController, + tvPipBoundsAlgorithm); + mPipTransitionState = pipTransitionState; + mPipAnimationController = pipAnimationController; + mSurfaceTransactionHelper = pipSurfaceTransactionHelper; + mTvPipMenuController = tvPipMenuController; + mPipDisplayLayoutState = pipDisplayLayoutState; + mTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); + + mEnterFadeOutDuration = context.getResources().getInteger( + R.integer.config_tvPipEnterFadeOutDuration); + mEnterFadeInDuration = context.getResources().getInteger( + R.integer.config_tvPipEnterFadeInDuration); + mExitFadeOutDuration = context.getResources().getInteger( + R.integer.config_tvPipExitFadeOutDuration); + mExitFadeInDuration = context.getResources().getInteger( + R.integer.config_tvPipExitFadeInDuration); + } + + @Override + public void startExitTransition(int type, WindowContainerTransaction out, + @Nullable Rect destinationBounds) { + cancelAnimations(); + mPendingExitTransition = mTransitions.startTransition(type, out, this); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + + if (isCloseTransition(info)) { + // PiP is closing (without reentering fullscreen activity) + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Starting close animation", TAG); + cancelAnimations(); + startCloseAnimation(info, startTransaction, finishTransaction, finishCallback); + mCurrentPipTaskToken = null; + return true; + + } else if (transition.equals(mPendingExitTransition)) { + // PiP is exiting (reentering fullscreen activity) + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Starting exit animation", TAG); + + final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info); + mPendingExitTransition = null; + // PipTaskChange can be null if the PIP task has been detached, for example, when the + // task contains multiple activities, the PIP will be moved to a new PIP task when + // entering, and be moved back when exiting. In that case, the PIP task will be removed + // immediately. + final TaskInfo pipTaskInfo = currentPipTaskChange != null + ? currentPipTaskChange.getTaskInfo() + : mPipOrganizer.getTaskInfo(); + if (pipTaskInfo == null) { + throw new RuntimeException("Cannot find the pip task for exit-pip transition."); + } + + final int type = info.getType(); + switch (type) { + case TRANSIT_EXIT_PIP -> { + TransitionInfo.Change pipChange = currentPipTaskChange; + SurfaceControl activitySc = null; + if (mCurrentPipTaskToken == null) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG); + } else if (pipChange == null) { + // The pipTaskChange is null, this can happen if we are reparenting the + // PIP activity back to its original Task. In that case, we should animate + // the activity leash instead, which should be the change whose last parent + // is the recorded PiP Task. + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (mCurrentPipTaskToken.equals(change.getLastParent())) { + // Find the activity that is exiting PiP. + pipChange = change; + activitySc = change.getLeash(); + break; + } + } + } + if (pipChange == null) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: No window of exiting PIP is found. Can't play expand " + + "animation", + TAG); + removePipImmediately(info, pipTaskInfo, startTransaction, finishTransaction, + finishCallback); + return true; + } + final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info); + final SurfaceControl pipLeash; + if (activitySc != null) { + // Use a local leash to animate activity in case the activity has + // letterbox which may be broken by PiP animation, e.g. always end at 0,0 + // in parent and unable to include letterbox area in crop bounds. + final SurfaceControl activitySurface = pipChange.getLeash(); + pipLeash = new SurfaceControl.Builder() + .setName(activitySc + "_pip-leash") + .setContainerLayer() + .setHidden(false) + .setParent(root.getLeash()) + .build(); + startTransaction.reparent(activitySurface, pipLeash); + // Put the activity at local position with offset in case it is letterboxed. + final Point activityOffset = pipChange.getEndRelOffset(); + startTransaction.setPosition(activitySc, activityOffset.x, + activityOffset.y); + } else { + pipLeash = pipChange.getLeash(); + startTransaction.reparent(pipLeash, root.getLeash()); + } + startTransaction.setLayer(pipLeash, Integer.MAX_VALUE); + final Rect currentBounds = mPipBoundsState.getBounds(); + final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds()); + cancelAnimations(); + startExitAnimation(pipTaskInfo, pipLeash, currentBounds, destinationBounds, + startTransaction, + finishTransaction, finishCallback); + } + // pass through here is intended + case TRANSIT_TO_BACK, TRANSIT_REMOVE_PIP -> removePipImmediately(info, pipTaskInfo, + startTransaction, finishTransaction, + finishCallback + ); + default -> { + return false; + } + } + mCurrentPipTaskToken = null; + return true; + + } else if (isEnteringPip(info)) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Starting enter animation", TAG); + + // Search for an Enter PiP transition + TransitionInfo.Change enterPip = null; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) { + enterPip = change; + } + } + if (enterPip == null) { + throw new IllegalStateException("Trying to start PiP animation without a pip" + + "participant"); + } + + // Make sure other open changes are visible as entering PIP. Some may be hidden in + // Transitions#setupStartState because the transition type is OPEN (such as auto-enter). + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change == enterPip) continue; + if (TransitionUtil.isOpeningType(change.getMode())) { + final SurfaceControl leash = change.getLeash(); + startTransaction.show(leash).setAlpha(leash, 1.f); + } + } + + cancelAnimations(); + startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback); + return true; + } + + return false; + } + + /** + * For {@link Transitions#TRANSIT_REMOVE_PIP}, we just immediately remove the PIP Task. + */ + private void removePipImmediately(@NonNull TransitionInfo info, + @NonNull TaskInfo taskInfo, @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: removePipImmediately", TAG); + cancelAnimations(); + startTransaction.apply(); + finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), + mPipDisplayLayoutState.getDisplayBounds()); + mTvPipMenuController.detach(); + mPipOrganizer.onExitPipFinished(taskInfo); + finishCallback.onTransitionFinished(/* wct= */ null); + + mPipTransitionState.setTransitionState(UNDEFINED); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK); + } + + private void startCloseAnimation(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final TransitionInfo.Change pipTaskChange = findCurrentPipTaskChange(info); + final SurfaceControl pipLeash = pipTaskChange.getLeash(); + + final List<SurfaceControl> closeLeashes = new ArrayList<>(); + for (TransitionInfo.Change change : info.getChanges()) { + if (TransitionUtil.isClosingType(change.getMode()) && change != pipTaskChange) { + closeLeashes.add(change.getLeash()); + } + } + + final Rect pipBounds = mPipBoundsState.getBounds(); + mSurfaceTransactionHelper + .resetScale(startTransaction, pipLeash, pipBounds) + .crop(startTransaction, pipLeash, pipBounds) + .shadow(startTransaction, pipLeash, false); + + final SurfaceControl.Transaction transaction = mTransactionFactory.getTransaction(); + for (SurfaceControl leash : closeLeashes) { + startTransaction.setShadowRadius(leash, 0f); + } + + ValueAnimator closeFadeOutAnimator = createAnimator(); + closeFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT); + closeFadeOutAnimator.setDuration(mExitFadeOutDuration); + closeFadeOutAnimator.addUpdateListener( + animationUpdateListener(pipLeash).fadingOut().withMenu()); + for (SurfaceControl leash : closeLeashes) { + closeFadeOutAnimator.addUpdateListener(animationUpdateListener(leash).fadingOut()); + } + + closeFadeOutAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(@NonNull Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: close animation: start", TAG); + for (SurfaceControl leash : closeLeashes) { + startTransaction.setShadowRadius(leash, 0f); + } + startTransaction.apply(); + + mPipTransitionState.setTransitionState(EXITING_PIP); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_REMOVE_STACK); + } + + @Override + public void onAnimationCancel(@NonNull Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: close animation: cancel", TAG); + sendOnPipTransitionCancelled(TRANSITION_DIRECTION_REMOVE_STACK); + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: close animation: end", TAG); + mTvPipMenuController.detach(); + finishCallback.onTransitionFinished(null /* wct */); + transaction.close(); + mPipTransitionState.setTransitionState(UNDEFINED); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK); + + mCurrentAnimator = null; + } + }); + + closeFadeOutAnimator.start(); + mCurrentAnimator = closeFadeOutAnimator; + } + + @Override + public void startEnterAnimation(@NonNull TransitionInfo.Change pipChange, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + // Keep track of the PIP task + mCurrentPipTaskToken = pipChange.getContainer(); + final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo(); + final SurfaceControl leash = pipChange.getLeash(); + + mTvPipMenuController.attach(leash); + setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams, + taskInfo.topActivityInfo); + + final Rect pipBounds = + mPipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas(); + mPipBoundsState.setBounds(pipBounds); + mTvPipMenuController.movePipMenu(null, pipBounds, 0f); + + final WindowContainerTransaction resizePipWct = new WindowContainerTransaction(); + resizePipWct.setWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED); + resizePipWct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED); + resizePipWct.setBounds(taskInfo.token, pipBounds); + + mSurfaceTransactionHelper + .resetScale(finishTransaction, leash, pipBounds) + .crop(finishTransaction, leash, pipBounds) + .shadow(finishTransaction, leash, false); + + final Rect currentBounds = pipChange.getStartAbsBounds(); + final Rect fadeOutCurrentBounds = scaledRect(currentBounds, ZOOM_ANIMATION_SCALE_FACTOR); + + final ValueAnimator enterFadeOutAnimator = createAnimator(); + enterFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT); + enterFadeOutAnimator.setDuration(mEnterFadeOutDuration); + enterFadeOutAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingOut() + .animateBounds(currentBounds, fadeOutCurrentBounds, currentBounds)); + + enterFadeOutAnimator.addListener(new AnimatorListenerAdapter() { + @SuppressLint("MissingPermission") + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter fade out animation: end", TAG); + SurfaceControl.Transaction tx = mTransactionFactory.getTransaction(); + mSurfaceTransactionHelper + .resetScale(tx, leash, pipBounds) + .crop(tx, leash, pipBounds) + .shadow(tx, leash, false); + mShellTaskOrganizer.applyTransaction(resizePipWct); + tx.apply(); + } + }); + + final ValueAnimator enterFadeInAnimator = createAnimator(); + enterFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER); + enterFadeInAnimator.setDuration(mEnterFadeInDuration); + enterFadeInAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingIn() + .withMenu() + .atBounds(pipBounds)); + + final AnimatorSet animatorSet = new AnimatorSet(); + animatorSet + .play(enterFadeInAnimator) + .after(500) + .after(enterFadeOutAnimator); + + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter animation: start", TAG); + startTransaction.apply(); + mPipTransitionState.setTransitionState(ENTERING_PIP); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP); + } + + @Override + public void onAnimationCancel(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter animation: cancel", TAG); + enterFadeInAnimator.setCurrentFraction(1f); + sendOnPipTransitionCancelled(TRANSITION_DIRECTION_TO_PIP); + } + + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter animation: end", TAG); + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + wct.setBounds(taskInfo.token, pipBounds); + finishCallback.onTransitionFinished(wct); + + mPipTransitionState.setTransitionState(ENTERED_PIP); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); + mCurrentAnimator = null; + } + }); + + animatorSet.start(); + mCurrentAnimator = animatorSet; } + private void startExitAnimation(@NonNull TaskInfo taskInfo, SurfaceControl leash, + Rect currentBounds, Rect destinationBounds, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final Rect fadeInStartBounds = scaledRect(destinationBounds, ZOOM_ANIMATION_SCALE_FACTOR); + + final ValueAnimator exitFadeOutAnimator = createAnimator(); + exitFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT); + exitFadeOutAnimator.setDuration(mExitFadeOutDuration); + exitFadeOutAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingOut() + .withMenu() + .atBounds(currentBounds)); + exitFadeOutAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit fade out animation: end", TAG); + startTransaction.apply(); + mPipMenuController.detach(); + } + }); + + final ValueAnimator exitFadeInAnimator = createAnimator(); + exitFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER); + exitFadeInAnimator.setDuration(mExitFadeInDuration); + exitFadeInAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingIn() + .animateBounds(fadeInStartBounds, destinationBounds, destinationBounds)); + + final AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playSequentially( + exitFadeOutAnimator, + exitFadeInAnimator + ); + + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit animation: start", TAG); + mPipTransitionState.setTransitionState(EXITING_PIP); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_LEAVE_PIP); + } + + @Override + public void onAnimationCancel(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit animation: cancel", TAG); + sendOnPipTransitionCancelled(TRANSITION_DIRECTION_LEAVE_PIP); + } + + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit animation: end", TAG); + mPipOrganizer.onExitPipFinished(taskInfo); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + wct.setBounds(taskInfo.token, destinationBounds); + finishCallback.onTransitionFinished(wct); + + mPipTransitionState.setTransitionState(UNDEFINED); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP); + + mCurrentAnimator = null; + } + }); + + animatorSet.start(); + mCurrentAnimator = animatorSet; + } + + @NonNull + private ValueAnimator createAnimator() { + final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get()); + return animator; + } + + @NonNull + private TvPipTransitionAnimatorUpdateListener animationUpdateListener( + @NonNull SurfaceControl leash) { + return new TvPipTransitionAnimatorUpdateListener(leash, mTvPipMenuController, + mTransactionFactory.getTransaction(), mSurfaceTransactionHelper); + } + + @NonNull + private static Rect scaledRect(@NonNull Rect rect, float scale) { + final Rect out = new Rect(rect); + out.inset((int) (rect.width() * (1 - scale) / 2), (int) (rect.height() * (1 - scale) / 2)); + return out; + } + + private boolean isCloseTransition(TransitionInfo info) { + final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info); + return currentPipTaskChange != null && info.getType() == TRANSIT_CLOSE; + } + + @Nullable + private TransitionInfo.Change findCurrentPipTaskChange(@NonNull TransitionInfo info) { + if (mCurrentPipTaskToken == null) { + return null; + } + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (mCurrentPipTaskToken.equals(change.getContainer())) { + return change; + } + } + return null; + } + + /** + * Whether we should handle the given {@link TransitionInfo} animation as entering PIP. + */ + private boolean isEnteringPip(@NonNull TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (isEnteringPip(change, info.getType())) return true; + } + return false; + } + + /** + * Whether a particular change is a window that is entering pip. + */ + @Override + public boolean isEnteringPip(@NonNull TransitionInfo.Change change, + @WindowManager.TransitionType int transitType) { + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED + && !Objects.equals(change.getContainer(), mCurrentPipTaskToken)) { + if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN + || transitType == TRANSIT_CHANGE) { + return true; + } + // Please file a bug to handle the unexpected transition type. + android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type=" + + transitTypeToString(transitType), new Throwable()); + } + return false; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: merge animation", TAG); + if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { + mCurrentAnimator.end(); + } + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + if (requestHasPipEnter(request)) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: handle PiP enter request", TAG); + WindowContainerTransaction wct = new WindowContainerTransaction(); + augmentRequest(transition, request, wct); + return wct; + } else if (request.getType() == TRANSIT_TO_BACK && request.getTriggerTask() != null + && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_PINNED) { + // if we receive a TRANSIT_TO_BACK type of request while in PiP + mPendingExitTransition = transition; + + // update the transition state to avoid {@link PipTaskOrganizer#onTaskVanished()} calls + mPipTransitionState.setTransitionState(EXITING_PIP); + + // return an empty WindowContainerTransaction so that we don't check other handlers + return new WindowContainerTransaction(); + } else { + return null; + } + } + + @Override + public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request, + @NonNull WindowContainerTransaction outWCT) { + if (!requestHasPipEnter(request)) { + throw new IllegalStateException("Called PiP augmentRequest when request has no PiP"); + } + outWCT.setActivityWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_UNDEFINED); + } + + /** + * Cancel any ongoing PiP transitions/animations. + */ + public void cancelAnimations() { + if (mPipAnimationController.isAnimating()) { + mPipAnimationController.getCurrentAnimator().cancel(); + mPipAnimationController.resetAnimatorState(); + } + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + } + + @Override + public void end() { + if (mCurrentAnimator != null) { + mCurrentAnimator.end(); + } + } + + private static class TvPipTransitionAnimatorUpdateListener implements + ValueAnimator.AnimatorUpdateListener { + private final SurfaceControl mLeash; + private final TvPipMenuController mTvPipMenuController; + private final SurfaceControl.Transaction mTransaction; + private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; + private final RectF mTmpRectF = new RectF(); + private final Rect mTmpRect = new Rect(); + + private float mStartAlpha = ALPHA_NO_CHANGE; + private float mEndAlpha = ALPHA_NO_CHANGE; + + @Nullable + private Rect mStartBounds; + @Nullable + private Rect mEndBounds; + private Rect mWindowContainerBounds; + private boolean mShowMenu; + + TvPipTransitionAnimatorUpdateListener(@NonNull SurfaceControl leash, + @NonNull TvPipMenuController tvPipMenuController, + @NonNull SurfaceControl.Transaction transaction, + @NonNull PipSurfaceTransactionHelper pipSurfaceTransactionHelper) { + mLeash = leash; + mTvPipMenuController = tvPipMenuController; + mTransaction = transaction; + mSurfaceTransactionHelper = pipSurfaceTransactionHelper; + } + + public TvPipTransitionAnimatorUpdateListener animateAlpha( + @FloatRange(from = 0.0, to = 1.0) float startAlpha, + @FloatRange(from = 0.0, to = 1.0) float endAlpha) { + mStartAlpha = startAlpha; + mEndAlpha = endAlpha; + return this; + } + + public TvPipTransitionAnimatorUpdateListener animateBounds(@NonNull Rect startBounds, + @NonNull Rect endBounds, @NonNull Rect windowContainerBounds) { + mStartBounds = startBounds; + mEndBounds = endBounds; + mWindowContainerBounds = windowContainerBounds; + return this; + } + + public TvPipTransitionAnimatorUpdateListener atBounds(@NonNull Rect bounds) { + return animateBounds(bounds, bounds, bounds); + } + + public TvPipTransitionAnimatorUpdateListener fadingOut() { + return animateAlpha(1f, 0f); + } + + public TvPipTransitionAnimatorUpdateListener fadingIn() { + return animateAlpha(0f, 1f); + } + + public TvPipTransitionAnimatorUpdateListener withMenu() { + mShowMenu = true; + return this; + } + + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + final float fraction = animation.getAnimatedFraction(); + final float alpha = lerp(mStartAlpha, mEndAlpha, fraction); + if (mStartBounds != null && mEndBounds != null) { + lerp(mStartBounds, mEndBounds, fraction, mTmpRectF); + applyAnimatedValue(alpha, mTmpRectF); + } else { + applyAnimatedValue(alpha, null); + } + } + + private void applyAnimatedValue(float alpha, @Nullable RectF bounds) { + Trace.beginSection("applyAnimatedValue"); + final SurfaceControl.Transaction tx = mTransaction; + + Trace.beginSection("leash scale and alpha"); + if (alpha != ALPHA_NO_CHANGE) { + mSurfaceTransactionHelper.alpha(tx, mLeash, alpha); + } + if (bounds != null) { + mSurfaceTransactionHelper.scale(tx, mLeash, mWindowContainerBounds, bounds); + } + mSurfaceTransactionHelper.shadow(tx, mLeash, false); + tx.show(mLeash); + Trace.endSection(); + + if (mShowMenu) { + Trace.beginSection("movePipMenu"); + if (bounds != null) { + mTmpRect.set((int) bounds.left, (int) bounds.top, (int) bounds.right, + (int) bounds.bottom); + mTvPipMenuController.movePipMenu(tx, mTmpRect, alpha); + } else { + mTvPipMenuController.movePipMenu(tx, null, alpha); + } + Trace.endSection(); + } else { + mTvPipMenuController.movePipMenu(tx, null, 0f); + } + + tx.apply(); + Trace.endSection(); + } + + private float lerp(float start, float end, float fraction) { + return start * (1 - fraction) + end * fraction; + } + + private void lerp(@NonNull Rect start, @NonNull Rect end, float fraction, + @NonNull RectF out) { + out.set( + start.left * (1 - fraction) + end.left * fraction, + start.top * (1 - fraction) + end.top * fraction, + start.right * (1 - fraction) + end.right * fraction, + start.bottom * (1 - fraction) + end.bottom * fraction); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java index b528089d153e..5c02dbcb5255 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java @@ -17,6 +17,7 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static com.android.wm.shell.transition.Transitions.TransitionObserver; @@ -61,9 +62,10 @@ public class HomeTransitionObserver implements TransitionObserver, } final int mode = change.getMode(); + final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED); if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME - && TransitionUtil.isOpenOrCloseMode(mode)) { - notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode)); + && (TransitionUtil.isOpenOrCloseMode(mode) || isBackGesture)) { + notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode) || isBackGesture); } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index a5629c8f8f15..b355ab0bf311 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -14,8 +14,6 @@ import android.view.SurfaceControl import android.window.TransitionInfo import android.window.TransitionInfo.FLAG_IS_WALLPAPER import androidx.test.filters.SmallTest -import com.android.server.testutils.any -import com.android.server.testutils.mock import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder @@ -28,8 +26,10 @@ import java.util.function.Supplier import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Mock +import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.verifyZeroInteractions diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 4e2b7f6d16b2..800f9e4e5371 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -66,6 +66,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -283,7 +284,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper) .round(any(), any(), anyBoolean()); doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper) - .scale(any(), any(), any(), any(), anyFloat()); + .scale(any(), any(), any(), ArgumentMatchers.<Rect>any(), anyFloat()); doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper) .alpha(any(), any(), anyFloat()); doNothing().when(mMockPipSurfaceTransactionHelper).onDensityOrFontScaleChanged(any()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java index ea7c0d9c264e..421c44511a54 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java @@ -18,8 +18,10 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -145,6 +147,26 @@ public class HomeTransitionObserverTest extends ShellTestCase { verify(mListener, times(0)).onHomeVisibilityChanged(anyBoolean()); } + @Test + public void testHomeActivityWithBackGestureNotifiesHomeIsVisible() throws RemoteException { + TransitionInfo info = mock(TransitionInfo.class); + TransitionInfo.Change change = mock(TransitionInfo.Change.class); + ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); + when(change.getTaskInfo()).thenReturn(taskInfo); + when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change))); + + when(change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)).thenReturn(true); + setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_CHANGE); + + mHomeTransitionObserver.onTransitionReady(mock(IBinder.class), + info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + + verify(mListener, times(1)).onHomeVisibilityChanged(true); + } + + /** * Helper class to initialize variables for the rest. */ diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index d056248273b2..8748dab581bb 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -603,7 +603,7 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, std::unique_ptr<Asset> asset = assets->GetAssetsProvider()->Open(filename, mode); if (asset) { if (out_cookie != nullptr) { - *out_cookie = i; + *out_cookie = i - 1; } return asset; } diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index b63ee1bd3d98..a9d1a2aed8cc 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -25,8 +25,9 @@ #include "MinikinSkia.h" #include "SkPaint.h" -#include "SkStream.h" // Fot tests. +#include "SkStream.h" // For tests. #include "SkTypeface.h" +#include "utils/TypefaceUtils.h" #include <minikin/FontCollection.h> #include <minikin/FontFamily.h> @@ -186,7 +187,9 @@ void Typeface::setRobotoTypefaceForTest() { LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", kRobotoFont); void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0); std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(data, st.st_size)); - sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData)); + sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr(); + LOG_ALWAYS_FATAL_IF(fm == nullptr, "Could not load FreeType SkFontMgr"); + sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData)); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont); std::shared_ptr<minikin::MinikinFont> font = diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index de842e68b118..9f63dfdc0ccb 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1065,17 +1065,8 @@ public class AudioManager { * @see #isVolumeFixed() */ public void adjustVolume(int direction, @PublicVolumeFlags int flags) { - if (autoPublicVolumeApiHardening()) { - final IAudioService service = getService(); - try { - service.adjustVolume(direction, flags); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); - helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags); - } + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); + helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags); } /** @@ -1104,17 +1095,8 @@ public class AudioManager { */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, @PublicVolumeFlags int flags) { - if (autoPublicVolumeApiHardening()) { - final IAudioService service = getService(); - try { - service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); - helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags); - } + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); + helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags); } /** @hide */ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 11e3a088b5e2..b4ca485eb764 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -500,10 +500,6 @@ interface IAudioService { in String packageName, int uid, int pid, in UserHandle userHandle, int targetSdkVersion); - oneway void adjustVolume(int direction, int flags); - - oneway void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags); - boolean isMusicActive(in boolean remotely); int getDeviceMaskForStream(in int streamType); diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java index 5509782e2988..f52297450e85 100644 --- a/media/java/android/media/MediaMetrics.java +++ b/media/java/android/media/MediaMetrics.java @@ -148,6 +148,7 @@ public class MediaMetrics { createKey("headTrackerEnabled", String.class); // spatializer public static final Key<Integer> INDEX = createKey("index", Integer.class); // volume + public static final Key<Integer> OLD_INDEX = createKey("oldIndex", Integer.class); // volume public static final Key<Integer> INPUT_PORT_COUNT = createKey("inputPortCount", Integer.class); // MIDI // Either "true" or "false" diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 9f2a9ac4798d..fea6c5f95358 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -336,13 +336,6 @@ LIBANDROID { APerformanceHint_closeSession; # introduced=Tiramisu APerformanceHint_setThreads; # introduced=UpsideDownCake APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream - APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream - AWorkDuration_create; # introduced=VanillaIceCream - AWorkDuration_release; # introduced=VanillaIceCream - AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream - AWorkDuration_setActualTotalDurationNanos; # introduced=VanillaIceCream - AWorkDuration_setActualCpuDurationNanos; # introduced=VanillaIceCream - AWorkDuration_setActualGpuDurationNanos; # introduced=VanillaIceCream local: *; }; diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index c4c81284780e..c25df6e08fd0 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -18,14 +18,12 @@ #include <aidl/android/hardware/power/SessionHint.h> #include <aidl/android/hardware/power/SessionMode.h> -#include <android/WorkDuration.h> #include <android/os/IHintManager.h> #include <android/os/IHintSession.h> #include <android/performance_hint.h> #include <binder/Binder.h> #include <binder/IBinder.h> #include <binder/IServiceManager.h> -#include <inttypes.h> #include <performance_hint_private.h> #include <utils/SystemClock.h> @@ -77,13 +75,10 @@ public: int setThreads(const int32_t* threadIds, size_t size); int getThreadIds(int32_t* const threadIds, size_t* size); int setPreferPowerEfficiency(bool enabled); - int reportActualWorkDuration(AWorkDuration* workDuration); private: friend struct APerformanceHintManager; - int reportActualWorkDurationInternal(WorkDuration* workDuration); - sp<IHintManager> mHintManager; sp<IHintSession> mHintSession; // HAL preferred update rate @@ -97,7 +92,8 @@ private: // Last hint reported from sendHint indexed by hint value std::vector<int64_t> mLastHintSentTimestamp; // Cached samples - std::vector<WorkDuration> mActualWorkDurations; + std::vector<int64_t> mActualDurationsNanos; + std::vector<int64_t> mTimestampsNanos; }; static IHintManager* gIHintManagerForTesting = nullptr; @@ -199,7 +195,8 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano * Most of the workload is target_duration dependent, so now clear the cached samples * as they are most likely obsolete. */ - mActualWorkDurations.clear(); + mActualDurationsNanos.clear(); + mTimestampsNanos.clear(); mFirstTargetMetTimestamp = 0; mLastTargetMetTimestamp = 0; return 0; @@ -210,10 +207,43 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__); return EINVAL; } + int64_t now = elapsedRealtimeNano(); + mActualDurationsNanos.push_back(actualDurationNanos); + mTimestampsNanos.push_back(now); - WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0); + if (actualDurationNanos >= mTargetDurationNanos) { + // Reset timestamps if we are equal or over the target. + mFirstTargetMetTimestamp = 0; + } else { + // Set mFirstTargetMetTimestamp for first time meeting target. + if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp || + (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) { + mFirstTargetMetTimestamp = now; + } + /** + * Rate limit the change if the update is over mPreferredRateNanos since first + * meeting target and less than mPreferredRateNanos since last meeting target. + */ + if (now - mFirstTargetMetTimestamp > mPreferredRateNanos && + now - mLastTargetMetTimestamp <= mPreferredRateNanos) { + return 0; + } + mLastTargetMetTimestamp = now; + } - return reportActualWorkDurationInternal(&workDuration); + binder::Status ret = + mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos); + if (!ret.isOk()) { + ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__, + ret.exceptionMessage().c_str()); + mFirstTargetMetTimestamp = 0; + mLastTargetMetTimestamp = 0; + return EPIPE; + } + mActualDurationsNanos.clear(); + mTimestampsNanos.clear(); + + return 0; } int APerformanceHintSession::sendHint(SessionHint hint) { @@ -292,67 +322,6 @@ int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) { return OK; } -int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) { - WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration); - if (workDuration->workPeriodStartTimestampNanos <= 0) { - ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualTotalDurationNanos <= 0) { - ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualCpuDurationNanos <= 0) { - ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualGpuDurationNanos < 0) { - ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__); - return EINVAL; - } - - return reportActualWorkDurationInternal(workDuration); -} - -int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* workDuration) { - int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos; - int64_t now = uptimeNanos(); - workDuration->timestampNanos = now; - mActualWorkDurations.push_back(std::move(*workDuration)); - - if (actualTotalDurationNanos >= mTargetDurationNanos) { - // Reset timestamps if we are equal or over the target. - mFirstTargetMetTimestamp = 0; - } else { - // Set mFirstTargetMetTimestamp for first time meeting target. - if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp || - (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) { - mFirstTargetMetTimestamp = now; - } - /** - * Rate limit the change if the update is over mPreferredRateNanos since first - * meeting target and less than mPreferredRateNanos since last meeting target. - */ - if (now - mFirstTargetMetTimestamp > mPreferredRateNanos && - now - mLastTargetMetTimestamp <= mPreferredRateNanos) { - return 0; - } - mLastTargetMetTimestamp = now; - } - - binder::Status ret = mHintSession->reportActualWorkDuration2(mActualWorkDurations); - if (!ret.isOk()) { - ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__, - ret.exceptionMessage().c_str()); - mFirstTargetMetTimestamp = 0; - mLastTargetMetTimestamp = 0; - return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE; - } - mActualWorkDurations.clear(); - - return 0; -} - // ===================================== C API APerformanceHintManager* APerformanceHint_getManager() { return APerformanceHintManager::getInstance(); @@ -407,64 +376,6 @@ int APerformanceHint_setPreferPowerEfficiency(APerformanceHintSession* session, return session->setPreferPowerEfficiency(enabled); } -int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session, - AWorkDuration* workDuration) { - if (session == nullptr || workDuration == nullptr) { - ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration); - return EINVAL; - } - return session->reportActualWorkDuration(workDuration); -} - -AWorkDuration* AWorkDuration_create() { - WorkDuration* workDuration = new WorkDuration(); - return static_cast<AWorkDuration*>(workDuration); -} - -void AWorkDuration_release(AWorkDuration* aWorkDuration) { - if (aWorkDuration == nullptr) { - ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__); - } - delete aWorkDuration; -} - -void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration, - int64_t workPeriodStartTimestampNanos) { - if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos = - workPeriodStartTimestampNanos; -} - -void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration, - int64_t actualTotalDurationNanos) { - if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualTotalDurationNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos; -} - -void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration, - int64_t actualCpuDurationNanos) { - if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualCpuDurationNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos; -} - -void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration, - int64_t actualGpuDurationNanos) { - if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualGpuDurationNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos; -} - void APerformanceHint_setIHintManagerForTesting(void* iManager) { delete gHintManagerForTesting; gHintManagerForTesting = nullptr; diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index 4553b4919d2d..22d33b139ccf 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -16,7 +16,6 @@ #define LOG_TAG "PerformanceHintNativeTest" -#include <android/WorkDuration.h> #include <android/os/IHintManager.h> #include <android/os/IHintSession.h> #include <android/performance_hint.h> @@ -61,8 +60,6 @@ public: MOCK_METHOD(Status, setMode, (int32_t mode, bool enabled), (override)); MOCK_METHOD(Status, close, (), (override)); MOCK_METHOD(IBinder*, onAsBinder, (), (override)); - MOCK_METHOD(Status, reportActualWorkDuration2, - (const ::std::vector<android::os::WorkDuration>& workDurations), (override)); }; class PerformanceHintTest : public Test { @@ -123,7 +120,6 @@ TEST_F(PerformanceHintTest, TestSession) { std::vector<int64_t> actualDurations; actualDurations.push_back(20); EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1)); - EXPECT_CALL(*iSession, reportActualWorkDuration2(_)).Times(Exactly(1)); result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos); EXPECT_EQ(0, result); @@ -242,125 +238,4 @@ TEST_F(PerformanceHintTest, CreateZeroTargetDurationSession) { APerformanceHintSession* session = APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); ASSERT_TRUE(session); -} - -MATCHER_P(WorkDurationEq, expected, "") { - if (arg.size() != expected.size()) { - *result_listener << "WorkDuration vectors are different sizes. Expected: " - << expected.size() << ", Actual: " << arg.size(); - return false; - } - for (int i = 0; i < expected.size(); ++i) { - android::os::WorkDuration expectedWorkDuration = expected[i]; - android::os::WorkDuration actualWorkDuration = arg[i]; - if (!expectedWorkDuration.equalsWithoutTimestamp(actualWorkDuration)) { - *result_listener << "WorkDuration at [" << i << "] is different: " - << "Expected: " << expectedWorkDuration - << ", Actual: " << actualWorkDuration; - return false; - } - } - return true; -} - -TEST_F(PerformanceHintTest, TestAPerformanceHint_reportActualWorkDuration2) { - APerformanceHintManager* manager = createManager(); - - std::vector<int32_t> tids; - tids.push_back(1); - tids.push_back(2); - int64_t targetDuration = 56789L; - - StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>(); - sp<IHintSession> session_sp(iSession); - - EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _)) - .Times(Exactly(1)) - .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status()))); - - APerformanceHintSession* session = - APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); - ASSERT_TRUE(session); - - int64_t targetDurationNanos = 10; - EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1)); - int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos); - EXPECT_EQ(0, result); - - usleep(2); // Sleep for longer than preferredUpdateRateNanos. - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, 20, 13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(0, result); - } - - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(-1, 20, 13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(22, result); - } - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, -20, 13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(22, result); - } - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, 20, -13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(EINVAL, result); - } - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, 20, 13, -8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(EINVAL, result); - } - - EXPECT_CALL(*iSession, close()).Times(Exactly(1)); - APerformanceHint_closeSession(session); -} - -TEST_F(PerformanceHintTest, TestAWorkDuration) { - AWorkDuration* aWorkDuration = AWorkDuration_create(); - ASSERT_NE(aWorkDuration, nullptr); - - AWorkDuration_setWorkPeriodStartTimestampNanos(aWorkDuration, 1); - AWorkDuration_setActualTotalDurationNanos(aWorkDuration, 20); - AWorkDuration_setActualCpuDurationNanos(aWorkDuration, 13); - AWorkDuration_setActualGpuDurationNanos(aWorkDuration, 8); - AWorkDuration_release(aWorkDuration); -} +}
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt index 4533db674da2..3abdb6f9c9f2 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt @@ -37,17 +37,17 @@ val Intent.requestInfo: RequestInfo? RequestInfo::class.java ) -val Intent.getCredentialProviderDataList: List<ProviderData> +val Intent.getCredentialProviderDataList: List<GetCredentialProviderData> get() = this.extras?.getParcelableArrayList( ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, GetCredentialProviderData::class.java - ) ?: emptyList() + ) ?.filterIsInstance<GetCredentialProviderData>() ?: emptyList() -val Intent.createCredentialProviderDataList: List<ProviderData> +val Intent.createCredentialProviderDataList: List<CreateCredentialProviderData> get() = this.extras?.getParcelableArrayList( ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, CreateCredentialProviderData::class.java - ) ?: emptyList() + ) ?.filterIsInstance<CreateCredentialProviderData>() ?: emptyList() val Intent.resultReceiver: ResultReceiver? get() = this.getParcelableExtra( diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt index ee45fbb00ba6..d4bca2add6cb 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt @@ -18,7 +18,6 @@ package com.android.credentialmanager.mapper import android.content.Intent import android.credentials.ui.Entry -import android.credentials.ui.GetCredentialProviderData import androidx.credentials.provider.PasswordCredentialEntry import com.android.credentialmanager.factory.fromSlice import com.android.credentialmanager.ktx.getCredentialProviderDataList @@ -32,12 +31,10 @@ import com.google.common.collect.ImmutableMap fun Intent.toGet(): Request.Get { val credentialEntries = mutableListOf<Pair<String, Entry>>() for (providerData in getCredentialProviderDataList) { - if (providerData is GetCredentialProviderData) { - for (credentialEntry in providerData.credentialEntries) { - credentialEntries.add( - Pair(providerData.providerFlattenedComponentName, credentialEntry) - ) - } + for (credentialEntry in providerData.credentialEntries) { + credentialEntries.add( + Pair(providerData.providerFlattenedComponentName, credentialEntry) + ) } } diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm index 93a508263962..071f9f436e04 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm @@ -116,6 +116,9 @@ key W { base: 'w' shift, capslock: 'W' shift+capslock: 'w' + ralt: '\u1e83' + shift+ralt, capslock+ralt: '\u1e82' + shift+capslock+ralt: '\u1e83' } key E { @@ -147,6 +150,9 @@ key Y { base: 'y' shift, capslock: 'Y' shift+capslock: 'y' + ralt: '\u00fd' + shift+ralt, capslock+ralt: '\u00dd' + shift+capslock+ralt: '\u00fd' } key U { @@ -313,6 +319,9 @@ key C { base: 'c' shift, capslock: 'C' shift+capslock: 'c' + ralt: '\u00e7' + shift+ralt, capslock+ralt: '\u00c7' + shift+capslock+ralt: '\u00e7' } key V { diff --git a/packages/InputDevices/res/raw/keyboard_layout_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_french.kcm index 490630456bb2..636f98d8bb95 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm @@ -44,7 +44,7 @@ key 2 { label: '2' base: '\u00e9' shift: '2' - ralt: '~' + ralt: '\u0303' } key 3 { @@ -79,7 +79,7 @@ key 7 { label: '7' base: '\u00e8' shift: '7' - ralt: '`' + ralt: '\u0300' } key 8 { diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml index 7f23f747090b..ee49b23b3860 100644 --- a/packages/InputDevices/res/xml/keyboard_layouts.xml +++ b/packages/InputDevices/res/xml/keyboard_layouts.xml @@ -94,7 +94,7 @@ android:name="keyboard_layout_swiss_german" android:label="@string/keyboard_layout_swiss_german_label" android:keyboardLayout="@raw/keyboard_layout_swiss_german" - android:keyboardLocale="de-Latn-CH" + android:keyboardLocale="de-Latn-CH|gsw-Latn-CH" android:keyboardLayoutType="qwertz" /> <keyboard-layout diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 6e47689d585c..0d1c9b07bf55 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -181,6 +181,18 @@ <receiver android:name="androidx.profileinstaller.ProfileInstallReceiver" tools:node="remove" /> + + <activity android:name=".UnarchiveActivity" + android:configChanges="orientation|keyboardHidden|screenSize" + android:theme="@style/Theme.AlertDialogActivity.NoActionBar" + android:excludeFromRecents="true" + android:noHistory="true" + android:exported="true"> + <intent-filter android:priority="1"> + <action android:name="android.intent.action.UNARCHIVE_DIALOG" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> </application> </manifest> diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml index 4eaa39bcd3a4..0a2e8809a17c 100644 --- a/packages/PackageInstaller/res/values/strings.xml +++ b/packages/PackageInstaller/res/values/strings.xml @@ -257,4 +257,14 @@ <!-- Notification shown in status bar when an application is successfully installed. [CHAR LIMIT=50] --> <string name="notification_installation_success_status">Successfully installed \u201c<xliff:g id="appname" example="Package Installer">%1$s</xliff:g>\u201d</string> + + <!-- The title of a dialog which asks the user to restore (i.e. re-install, re-download) an app + after parts of the app have been previously moved into the cloud for temporary storage. + "installername" is the app that will facilitate the download of the app. [CHAR LIMIT=50] --> + <string name="unarchive_application_title">Restore <xliff:g id="appname" example="Bird Game">%1$s</xliff:g> from <xliff:g id="installername" example="App Store">%1$s</xliff:g>?</string> + <!-- After the user confirms the dialog, a download will start. [CHAR LIMIT=none] --> + <string name="unarchive_body_text">This app will begin to download in the background</string> + <!-- The action to restore (i.e. re-install, re-download) an app after parts of the app have been previously moved + into the cloud for temporary storage. [CHAR LIMIT=15] --> + <string name="restore">Restore</string> </resources> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java new file mode 100644 index 000000000000..754437e64e78 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller; + +import static android.Manifest.permission; +import static android.content.pm.PackageManager.GET_PERMISSIONS; +import static android.content.pm.PackageManager.MATCH_ARCHIVED_PACKAGES; + +import android.app.Activity; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.content.IntentSender; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Process; +import android.util.Log; + +import androidx.annotation.Nullable; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; + +public class UnarchiveActivity extends Activity { + + public static final String EXTRA_UNARCHIVE_INTENT_SENDER = + "android.content.pm.extra.UNARCHIVE_INTENT_SENDER"; + static final String APP_TITLE = "com.android.packageinstaller.unarchive.app_title"; + static final String INSTALLER_TITLE = "com.android.packageinstaller.unarchive.installer_title"; + + private static final String TAG = "UnarchiveActivity"; + + private String mPackageName; + private IntentSender mIntentSender; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(null); + + int callingUid = getLaunchedFromUid(); + if (callingUid == Process.INVALID_UID) { + // Cannot reach Package/ActivityManager. Aborting uninstall. + Log.e(TAG, "Could not determine the launching uid."); + + setResult(Activity.RESULT_FIRST_USER); + finish(); + return; + } + + String callingPackage = getPackageNameForUid(callingUid); + if (callingPackage == null) { + Log.e(TAG, "Package not found for originating uid " + callingUid); + setResult(Activity.RESULT_FIRST_USER); + finish(); + return; + } + + // We don't check the AppOpsManager here for REQUEST_INSTALL_PACKAGES because the requester + // is not the source of the installation. + boolean hasRequestInstallPermission = Arrays.asList(getRequestedPermissions(callingPackage)) + .contains(permission.REQUEST_INSTALL_PACKAGES); + boolean hasInstallPermission = getBaseContext().checkPermission(permission.INSTALL_PACKAGES, + 0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED; + if (!hasRequestInstallPermission && !hasInstallPermission) { + Log.e(TAG, "Uid " + callingUid + " does not have " + + permission.REQUEST_INSTALL_PACKAGES + " or " + + permission.INSTALL_PACKAGES); + setResult(Activity.RESULT_FIRST_USER); + finish(); + return; + } + + Bundle extras = getIntent().getExtras(); + mPackageName = extras.getString(PackageInstaller.EXTRA_PACKAGE_NAME); + mIntentSender = extras.getParcelable(EXTRA_UNARCHIVE_INTENT_SENDER, IntentSender.class); + Objects.requireNonNull(mPackageName); + Objects.requireNonNull(mIntentSender); + + PackageManager pm = getPackageManager(); + try { + String appTitle = pm.getApplicationInfo(mPackageName, + PackageManager.ApplicationInfoFlags.of( + MATCH_ARCHIVED_PACKAGES)).loadLabel(pm).toString(); + // TODO(ag/25387215) Get the real installer title here after fixing getInstallSource for + // archived apps. + showDialogFragment(appTitle, "installerTitle"); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Invalid packageName: " + e.getMessage()); + } + } + + @Nullable + private String[] getRequestedPermissions(String callingPackage) { + String[] requestedPermissions = null; + try { + requestedPermissions = getPackageManager() + .getPackageInfo(callingPackage, GET_PERMISSIONS).requestedPermissions; + } catch (PackageManager.NameNotFoundException e) { + // Should be unreachable because we've just fetched the packageName above. + Log.e(TAG, "Package not found for " + callingPackage); + } + return requestedPermissions; + } + + void startUnarchive() { + try { + getPackageManager().getPackageInstaller().requestUnarchive(mPackageName, mIntentSender); + } catch (PackageManager.NameNotFoundException | IOException e) { + Log.e(TAG, "RequestUnarchive failed with %s." + e.getMessage()); + } + } + + private void showDialogFragment(String appTitle, String installerAppTitle) { + FragmentTransaction ft = getFragmentManager().beginTransaction(); + Fragment prev = getFragmentManager().findFragmentByTag("dialog"); + if (prev != null) { + ft.remove(prev); + } + + Bundle args = new Bundle(); + args.putString(APP_TITLE, appTitle); + args.putString(INSTALLER_TITLE, installerAppTitle); + DialogFragment fragment = new UnarchiveFragment(); + fragment.setArguments(args); + fragment.show(ft, "dialog"); + } + + private String getPackageNameForUid(int sourceUid) { + String[] packagesForUid = getPackageManager().getPackagesForUid(sourceUid); + if (packagesForUid == null) { + return null; + } + return packagesForUid[0]; + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java new file mode 100644 index 000000000000..6ccbc4cb5e6b --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java @@ -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.packageinstaller; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.os.Bundle; + +public class UnarchiveFragment extends DialogFragment implements + DialogInterface.OnClickListener { + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + String appTitle = getArguments().getString(UnarchiveActivity.APP_TITLE); + + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()); + + dialogBuilder.setTitle( + String.format(getContext().getString(R.string.unarchive_application_title), + appTitle)); + dialogBuilder.setMessage(R.string.unarchive_body_text); + + dialogBuilder.setPositiveButton(R.string.restore, this); + dialogBuilder.setNegativeButton(android.R.string.cancel, this); + + return dialogBuilder.create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == Dialog.BUTTON_POSITIVE) { + ((UnarchiveActivity) getActivity()).startUnarchive(); + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + if (isAdded()) { + getActivity().finish(); + } + } +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index b1e1585d4250..90c7d46c3004 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -49,6 +49,7 @@ import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvid import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider +import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver @@ -100,6 +101,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { SettingsTextFieldPasswordPageProvider, SearchScaffoldPageProvider, CardPageProvider, + CopyablePageProvider, ), rootPages = listOf( HomePageProvider.createSettingsPage(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt index f52ceec41253..1d897f77011e 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt @@ -44,6 +44,7 @@ import com.android.settingslib.spa.gallery.page.SliderPageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider +import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider import com.android.settingslib.spa.widget.scaffold.HomeScaffold @@ -71,6 +72,7 @@ object HomePageProvider : SettingsPageProvider { AlertDialogPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), CardPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ) } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt new file mode 100644 index 000000000000..f897d8c58030 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt @@ -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 com.android.settingslib.spa.gallery.ui + +import android.os.Bundle +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.settingslib.spa.framework.common.EntrySearchData +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold +import com.android.settingslib.spa.widget.ui.CopyableBody + +private const val TITLE = "Sample Copyable" + +object CopyablePageProvider : SettingsPageProvider { + override val name = "Copyable" + + private val owner = createSettingsPage() + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + .setSearchDataFn { EntrySearchData(title = TITLE) } + } + + @Composable + override fun Page(arguments: Bundle?) { + RegularScaffold(title = TITLE) { + Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) { + CopyableBody(body = "Copyable body") + } + } + } +} diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index 905640f1ca4b..b40e911bd8de 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -15,7 +15,7 @@ # [versions] -agp = "8.1.3" +agp = "8.1.4" compose-compiler = "1.5.1" dexmaker-mockito = "2.28.3" jvm = "17" diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt new file mode 100644 index 000000000000..3991f26e1b0c --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt @@ -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 com.android.settingslib.spa.framework.compose + +import androidx.activity.OnBackPressedCallback +import androidx.activity.OnBackPressedDispatcher +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.platform.LocalLifecycleOwner + +/** + * An effect for detecting presses of the system back button, and the back event will not be + * consumed by this effect. + * + * Calling this in your composable adds the given lambda to the [OnBackPressedDispatcher] of the + * [LocalOnBackPressedDispatcherOwner]. + * + * @param onBack the action invoked by pressing the system back + */ +@Composable +fun OnBackEffect(onBack: () -> Unit) { + val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) { + "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" + }.onBackPressedDispatcher + + // Safely update the current `onBack` lambda when a new one is provided + val currentOnBack by rememberUpdatedState(onBack) + // Remember in Composition a back callback that calls the `onBack` lambda + val backCallback = remember { + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + remove() + currentOnBack() + backDispatcher.onBackPressed() + } + } + } + val lifecycleOwner = LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner, backDispatcher) { + // Add callback to the backDispatcher + backDispatcher.addCallback(lifecycleOwner, backCallback) + // When the effect leaves the Composition, remove the callback + onDispose { + backCallback.remove() + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt new file mode 100644 index 000000000000..930d0a1872ab --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.spa.widget.ui + +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.DpOffset +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsTheme + +@Composable +fun CopyableBody(body: String) { + var expanded by remember { mutableStateOf(false) } + var dpOffset by remember { mutableStateOf(DpOffset.Unspecified) } + + Box(modifier = Modifier + .fillMaxWidth() + .pointerInput(Unit) { + detectTapGestures( + onLongPress = { + dpOffset = DpOffset(it.x.toDp(), it.y.toDp()) + expanded = true + }, + ) + } + ) { + SettingsBody(body) + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + offset = dpOffset, + ) { + DropdownMenuTitle(body) + DropdownMenuCopy(body) { expanded = false } + } + } +} + +@Composable +private fun DropdownMenuTitle(text: String) { + Text( + text = text, + modifier = Modifier + .padding(MenuDefaults.DropdownMenuItemContentPadding) + .padding( + top = SettingsDimension.itemPaddingAround, + bottom = SettingsDimension.buttonPaddingVertical, + ), + color = SettingsTheme.colorScheme.categoryTitle, + style = MaterialTheme.typography.labelMedium, + ) +} + +@Composable +private fun DropdownMenuCopy(body: String, onCopy: () -> Unit) { + val clipboardManager = LocalClipboardManager.current + DropdownMenuItem( + text = { + Text( + text = stringResource(android.R.string.copy), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + ) + }, + onClick = { + onCopy() + clipboardManager.setText(AnnotatedString(body)) + } + ) +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OnBackEffectTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OnBackEffectTest.kt new file mode 100644 index 000000000000..588168636828 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OnBackEffectTest.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.settingslib.spa.framework.compose + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.waitUntilExists +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class OnBackEffectTest { + @get:Rule + val composeTestRule = createComposeRule() + + private var onBackEffectCalled = false + + @Test + fun onBackEffect() { + composeTestRule.setContent { + TestNavHost { + val navController = LocalNavController.current + LaunchedEffect(Unit) { + navController.navigate(ROUTE_B) + delay(100) + navController.navigateBack() + } + } + } + + composeTestRule.waitUntilExists(hasText(ROUTE_A)) + assertThat(onBackEffectCalled).isTrue() + } + + @Composable + private fun TestNavHost(content: @Composable () -> Unit) { + val navController = rememberNavController() + CompositionLocalProvider(navController.localNavController()) { + NavHost(navController, ROUTE_A) { + composable(route = ROUTE_A) { Text(ROUTE_A) } + composable(route = ROUTE_B) { + Text(ROUTE_B) + + OnBackEffect { + onBackEffectCalled = true + } + } + } + content() + } + } + + private companion object { + const val ROUTE_A = "RouteA" + const val ROUTE_B = "RouteB" + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt new file mode 100644 index 000000000000..71072a5d58c5 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.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.settingslib.spa.widget.ui + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.longClick +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTouchInput +import androidx.test.core.app.ApplicationProvider +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 CopyableBodyTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun text_isDisplayed() { + composeTestRule.setContent { + CopyableBody(TEXT) + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + @Test + fun onLongPress_contextMenuDisplayed() { + composeTestRule.setContent { + CopyableBody(TEXT) + } + + composeTestRule.onNodeWithText(TEXT).performTouchInput { + longClick() + } + + composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).assertIsDisplayed() + } + + @Test + fun onCopy_saveToClipboard() { + val clipboardManager = context.getSystemService(ClipboardManager::class.java)!! + clipboardManager.setPrimaryClip(ClipData.newPlainText("", "")) + composeTestRule.setContent { + CopyableBody(TEXT) + } + + composeTestRule.onNodeWithText(TEXT).performTouchInput { + longClick() + } + composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).performClick() + + assertThat(clipboardManager.primaryClip!!.getItemAt(0).text.toString()).isEqualTo(TEXT) + } + + private companion object { + const val TEXT = "Text" + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index fc10a27cfa5b..45295b013cf9 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -36,10 +36,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import com.android.settingslib.development.DevelopmentSettingsEnabler import com.android.settingslib.spa.framework.compose.rememberDrawablePainter import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.ui.CopyableBody import com.android.settingslib.spa.widget.ui.SettingsBody import com.android.settingslib.spa.widget.ui.SettingsTitle import com.android.settingslib.spaprivileged.R @@ -71,26 +71,38 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { @Composable private fun InstallType(app: ApplicationInfo) { if (!app.isInstantApp) return - Spacer(modifier = Modifier.height(4.dp)) - SettingsBody(stringResource(com.android.settingslib.widget.preference.app.R.string.install_type_instant)) + Spacer(modifier = Modifier.height(SettingsDimension.paddingSmall)) + SettingsBody( + stringResource( + com.android.settingslib.widget.preference.app.R.string.install_type_instant + ) + ) } @Composable private fun AppVersion() { - if (packageInfo.versionName == null) return - Spacer(modifier = Modifier.height(4.dp)) - SettingsBody(packageInfo.versionNameBidiWrapped) + val versionName = packageInfo.versionNameBidiWrapped ?: return + Spacer(modifier = Modifier.height(SettingsDimension.paddingSmall)) + SettingsBody(versionName) } @Composable fun FooterAppVersion(showPackageName: Boolean = rememberIsDevelopmentSettingsEnabled()) { - if (packageInfo.versionName == null) return - HorizontalDivider() - Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) { - SettingsBody(stringResource(R.string.version_text, packageInfo.versionNameBidiWrapped)) + val context = LocalContext.current + val footer = remember(showPackageName) { + val list = mutableListOf<String>() + packageInfo.versionNameBidiWrapped?.let { + list += context.getString(R.string.version_text, it) + } if (showPackageName) { - SettingsBody(packageInfo.packageName) + list += packageInfo.packageName } + list.joinToString(separator = System.lineSeparator()) + } + if (footer.isBlank()) return + HorizontalDivider() + Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) { + CopyableBody(footer) } } @@ -104,7 +116,7 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { private companion object { /** Wrapped the version name, so its directionality still keep same when RTL. */ - val PackageInfo.versionNameBidiWrapped: String + val PackageInfo.versionNameBidiWrapped: String? get() = BidiFormatter.getInstance().unicodeWrap(versionName) } } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt index ab34f6820014..72a5bd76e737 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt @@ -105,7 +105,8 @@ class AppInfoTest { } } - composeTestRule.onNodeWithText("version $VERSION_NAME").assertIsDisplayed() + composeTestRule.onNodeWithText(text = "version $VERSION_NAME", substring = true) + .assertIsDisplayed() } @Test @@ -119,10 +120,10 @@ class AppInfoTest { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { - appInfoProvider.FooterAppVersion(true) + appInfoProvider.FooterAppVersion(showPackageName = true) } } - composeTestRule.onNodeWithText(PACKAGE_NAME).assertIsDisplayed() + composeTestRule.onNodeWithText(text = PACKAGE_NAME, substring = true).assertIsDisplayed() } @@ -137,10 +138,10 @@ class AppInfoTest { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { - appInfoProvider.FooterAppVersion(false) + appInfoProvider.FooterAppVersion(showPackageName = false) } } - composeTestRule.onNodeWithText(PACKAGE_NAME).assertDoesNotExist() + composeTestRule.onNodeWithText(text = PACKAGE_NAME, substring = true).assertDoesNotExist() } private companion object { diff --git a/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml b/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml new file mode 100644 index 000000000000..bcf5b91797d1 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml @@ -0,0 +1,29 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal" > + <path + android:fillColor="#4E4639" + android:pathData="M21.818,18.14V15.227C21.818,12.522 19.615,10.318 16.909,10.318C14.204,10.318 12,12.522 12,15.227V19.591C12,22.296 14.204,24.5 16.909,24.5C17.662,24.5 18.382,24.326 19.025,24.02C19.538,24.326 20.127,24.5 20.727,24.5C22.527,24.5 24,23.028 24,21.228C24,19.809 23.084,18.587 21.818,18.14ZM16.909,12.5C18.414,12.5 19.636,13.722 19.636,15.227C19.636,16.733 18.414,17.955 16.909,17.955C15.404,17.955 14.182,16.733 14.182,15.227C14.182,13.722 15.404,12.5 16.909,12.5ZM14.182,19.591V19.308C14.967,19.831 15.906,20.136 16.909,20.136C17.913,20.136 18.851,19.831 19.636,19.308V19.591C19.636,19.635 19.636,19.678 19.636,19.711C19.636,19.722 19.636,19.733 19.636,19.744C19.636,19.777 19.636,19.82 19.625,19.853C19.625,19.864 19.625,19.886 19.625,19.896C19.625,19.918 19.615,19.951 19.615,19.973C19.615,19.995 19.604,20.028 19.604,20.049C19.604,20.06 19.593,20.082 19.593,20.093C19.549,20.3 19.494,20.497 19.407,20.693C19.385,20.736 19.364,20.78 19.342,20.824C19.342,20.835 19.331,20.846 19.331,20.846C19.32,20.867 19.309,20.889 19.298,20.911C19.287,20.933 19.265,20.966 19.254,20.987C19.244,20.998 19.244,21.009 19.233,21.02C19.211,21.053 19.2,21.075 19.178,21.107C19.178,21.107 19.178,21.108 19.178,21.118C18.687,21.838 17.858,22.318 16.92,22.318C15.404,22.318 14.182,21.097 14.182,19.591Z" /> + <path + android:fillColor="#4E4639" + android:pathData="M12,5.409C12,2.704 9.796,0.5 7.091,0.5C4.385,0.5 2.182,2.704 2.182,5.409V8.322C0.916,8.769 0,9.991 0,11.409C0,13.209 1.473,14.682 3.273,14.682C3.873,14.682 4.462,14.507 4.975,14.202C5.618,14.507 6.338,14.682 7.091,14.682C9.796,14.682 12,12.478 12,9.773V5.409ZM7.091,2.682C8.596,2.682 9.818,3.904 9.818,5.409C9.818,6.915 8.596,8.136 7.091,8.136C5.585,8.136 4.364,6.915 4.364,5.409C4.364,3.904 5.585,2.682 7.091,2.682ZM7.091,12.5C6.153,12.5 5.324,12.02 4.833,11.3C4.833,11.3 4.833,11.3 4.833,11.289C4.811,11.256 4.8,11.234 4.778,11.202C4.767,11.191 4.767,11.18 4.756,11.169C4.745,11.147 4.724,11.115 4.713,11.093C4.702,11.071 4.691,11.049 4.68,11.027C4.68,11.016 4.669,11.005 4.669,11.005C4.647,10.962 4.625,10.918 4.604,10.875C4.516,10.678 4.451,10.482 4.418,10.274C4.418,10.264 4.407,10.242 4.407,10.231C4.407,10.209 4.396,10.176 4.396,10.155C4.396,10.133 4.385,10.1 4.385,10.078C4.385,10.067 4.385,10.045 4.385,10.035C4.385,10.002 4.375,9.958 4.375,9.925C4.375,9.915 4.375,9.904 4.375,9.893C4.364,9.86 4.364,9.816 4.364,9.773V9.489C5.149,10.013 6.087,10.318 7.091,10.318C8.095,10.318 9.033,10.013 9.818,9.489V9.773C9.818,11.278 8.596,12.5 7.091,12.5Z" /> +</vector> + diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java index f83e37b2fd5c..3774b88db93d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java @@ -37,9 +37,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; -/** - * VolumeControlProfile handles Bluetooth Volume Control Controller role - */ +/** VolumeControlProfile handles Bluetooth Volume Control Controller role */ public class VolumeControlProfile implements LocalBluetoothProfile { private static final String TAG = "VolumeControlProfile"; private static boolean DEBUG = true; @@ -77,8 +75,8 @@ public class VolumeControlProfile implements LocalBluetoothProfile { } device = mDeviceManager.addDevice(nextDevice); } - device.onProfileStateChanged(VolumeControlProfile.this, - BluetoothProfile.STATE_CONNECTED); + device.onProfileStateChanged( + VolumeControlProfile.this, BluetoothProfile.STATE_CONNECTED); device.refresh(); } @@ -95,32 +93,36 @@ public class VolumeControlProfile implements LocalBluetoothProfile { } } - VolumeControlProfile(Context context, CachedBluetoothDeviceManager deviceManager, + VolumeControlProfile( + Context context, + CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager) { mContext = context; mDeviceManager = deviceManager; mProfileManager = profileManager; - BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, - new VolumeControlProfile.VolumeControlProfileServiceListener(), - BluetoothProfile.VOLUME_CONTROL); + BluetoothAdapter.getDefaultAdapter() + .getProfileProxy( + context, + new VolumeControlProfile.VolumeControlProfileServiceListener(), + BluetoothProfile.VOLUME_CONTROL); } - /** - * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the - * operation of this profile. + * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the operation + * of this profile. * - * Repeated registration of the same <var>callback</var> object will have no effect after - * the first call to this method, even when the <var>executor</var> is different. API caller - * would have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with - * the same callback object before registering it again. + * <p>Repeated registration of the same <var>callback</var> object will have no effect after the + * first call to this method, even when the <var>executor</var> is different. API caller would + * have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with the same + * callback object before registering it again. * * @param executor an {@link Executor} to execute given callback * @param callback user implementation of the {@link BluetoothVolumeControl.Callback} * @throws IllegalArgumentException if a null executor or callback is given */ - public void registerCallback(@NonNull @CallbackExecutor Executor executor, + public void registerCallback( + @NonNull @CallbackExecutor Executor executor, @NonNull BluetoothVolumeControl.Callback callback) { if (mService == null) { Log.w(TAG, "Proxy not attached to service. Cannot register callback."); @@ -131,8 +133,9 @@ public class VolumeControlProfile implements LocalBluetoothProfile { /** * Unregisters the specified {@link BluetoothVolumeControl.Callback}. - * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling - * {@link #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used. + * + * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling {@link + * #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used. * * <p>Callbacks are automatically unregistered when application process goes away * @@ -153,8 +156,8 @@ public class VolumeControlProfile implements LocalBluetoothProfile { * @param device {@link BluetoothDevice} representing the remote device * @param volumeOffset volume offset to be set on the remote device */ - public void setVolumeOffset(BluetoothDevice device, - @IntRange(from = -255, to = 255) int volumeOffset) { + public void setVolumeOffset( + BluetoothDevice device, @IntRange(from = -255, to = 255) int volumeOffset) { if (mService == null) { Log.w(TAG, "Proxy not attached to service. Cannot set volume offset."); return; @@ -165,16 +168,13 @@ public class VolumeControlProfile implements LocalBluetoothProfile { } mService.setVolumeOffset(device, volumeOffset); } - /** - * Provides information about the possibility to set volume offset on the remote device. - * If the remote device supports Volume Offset Control Service, it is automatically - * connected. + * Provides information about the possibility to set volume offset on the remote device. If the + * remote device supports Volume Offset Control Service, it is automatically connected. * * @param device {@link BluetoothDevice} representing the remote device * @return {@code true} if volume offset function is supported and available to use on the - * remote device. When Bluetooth is off, the return value should always be - * {@code false}. + * remote device. When Bluetooth is off, the return value should always be {@code false}. */ public boolean isVolumeOffsetAvailable(BluetoothDevice device) { if (mService == null) { @@ -188,6 +188,28 @@ public class VolumeControlProfile implements LocalBluetoothProfile { return mService.isVolumeOffsetAvailable(device); } + /** + * Tells the remote device to set a volume. + * + * @param device {@link BluetoothDevice} representing the remote device + * @param volume volume to be set on the remote device + * @param isGroupOp whether to set the volume to remote devices within the same CSIP group + */ + public void setDeviceVolume( + BluetoothDevice device, + @IntRange(from = 0, to = 255) int volume, + boolean isGroupOp) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot set volume offset."); + return; + } + if (device == null) { + Log.w(TAG, "Device is null. Cannot set volume offset."); + return; + } + mService.setDeviceVolume(device, volume, isGroupOp); + } + @Override public boolean accessProfileEnabled() { return false; @@ -199,10 +221,9 @@ public class VolumeControlProfile implements LocalBluetoothProfile { } /** - * Gets VolumeControlProfile devices matching connection states{ - * {@code BluetoothProfile.STATE_CONNECTED}, - * {@code BluetoothProfile.STATE_CONNECTING}, - * {@code BluetoothProfile.STATE_DISCONNECTING}} + * Gets VolumeControlProfile devices matching connection states{ {@code + * BluetoothProfile.STATE_CONNECTED}, {@code BluetoothProfile.STATE_CONNECTING}, {@code + * BluetoothProfile.STATE_DISCONNECTING}} * * @return Matching device list */ @@ -211,8 +232,11 @@ public class VolumeControlProfile implements LocalBluetoothProfile { return new ArrayList<BluetoothDevice>(0); } return mService.getDevicesMatchingConnectionStates( - new int[]{BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_DISCONNECTING}); + new int[] { + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING + }); } @Override @@ -285,7 +309,7 @@ public class VolumeControlProfile implements LocalBluetoothProfile { @Override public int getSummaryResourceForDevice(BluetoothDevice device) { - return 0; // VCP profile not displayed in UI + return 0; // VCP profile not displayed in UI } @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java index c56062739735..fe1529d11cd8 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java @@ -54,18 +54,14 @@ import java.util.concurrent.Executor; public class VolumeControlProfileTest { private static final int TEST_VOLUME_OFFSET = 10; + private static final int TEST_VOLUME_VALUE = 10; - @Rule - public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); - @Mock - private CachedBluetoothDeviceManager mDeviceManager; - @Mock - private LocalBluetoothProfileManager mProfileManager; - @Mock - private BluetoothDevice mBluetoothDevice; - @Mock - private BluetoothVolumeControl mService; + @Mock private CachedBluetoothDeviceManager mDeviceManager; + @Mock private LocalBluetoothProfileManager mProfileManager; + @Mock private BluetoothDevice mBluetoothDevice; + @Mock private BluetoothVolumeControl mService; private final Context mContext = ApplicationProvider.getApplicationContext(); private BluetoothProfile.ServiceListener mServiceListener; @@ -177,14 +173,14 @@ public class VolumeControlProfileTest { @Test public void getConnectedDevices_returnCorrectList() { mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); - int[] connectedStates = new int[] { - BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_DISCONNECTING}; - List<BluetoothDevice> connectedList = Arrays.asList( - mBluetoothDevice, - mBluetoothDevice, - mBluetoothDevice); + int[] connectedStates = + new int[] { + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING + }; + List<BluetoothDevice> connectedList = + Arrays.asList(mBluetoothDevice, mBluetoothDevice, mBluetoothDevice); when(mService.getDevicesMatchingConnectionStates(connectedStates)) .thenReturn(connectedList); @@ -222,6 +218,16 @@ public class VolumeControlProfileTest { } @Test + public void setDeviceVolume_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + + mProfile.setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true); + + verify(mService) + .setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true); + } + + @Test public void isVolumeOffsetAvailable_verifyIsCalledAndReturnTrue() { mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(true); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 2c15fc6a9cfd..8412cbaaea36 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -249,6 +249,8 @@ public class SecureSettings { Settings.Secure.HUB_MODE_TUTORIAL_STATE, Settings.Secure.STYLUS_BUTTONS_ENABLED, Settings.Secure.STYLUS_HANDWRITING_ENABLED, - Settings.Secure.DEFAULT_NOTE_TASK_PROFILE + Settings.Secure.DEFAULT_NOTE_TASK_PROFILE, + Settings.Secure.CREDENTIAL_SERVICE, + Settings.Secure.CREDENTIAL_SERVICE_PRIMARY }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 71c2ddc6de1f..9197554662d3 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -19,11 +19,13 @@ package android.provider.settings.validators; import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.AUTOFILL_SERVICE_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.JSON_OBJECT_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR; @@ -62,7 +64,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ADAPTIVE_CHARGING_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.CAMERA_AUTOROTATE, BOOLEAN_VALIDATOR); - VALIDATORS.put(Secure.AUTOFILL_SERVICE, NULLABLE_COMPONENT_NAME_VALIDATOR); VALIDATORS.put( Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, new InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE)); @@ -398,5 +399,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED, new DiscreteValueValidator(new String[] {"-1", "0", "1"})); VALIDATORS.put(Secure.DEFAULT_NOTE_TASK_PROFILE, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.CREDENTIAL_SERVICE, CREDENTIAL_SERVICE_VALIDATOR); + VALIDATORS.put(Secure.CREDENTIAL_SERVICE_PRIMARY, NULLABLE_COMPONENT_NAME_VALIDATOR); + VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java index 49012b0159c2..a8a659ee1e5c 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java @@ -235,4 +235,30 @@ public class SettingsValidators { } } }; + + static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() { + @Override + public boolean validate(String value) { + if (value == null || value.equals("")) { + return true; + } + + return COLON_SEPARATED_COMPONENT_LIST_VALIDATOR.validate(value); + } + }; + + static final Validator AUTOFILL_SERVICE_VALIDATOR = new Validator() { + @Override + public boolean validate(String value) { + if (value == null || value.equals("")) { + return true; + } + + if (value.equals("credential-provider")) { + return true; + } + + return NULLABLE_COMPONENT_NAME_VALIDATOR.validate(value); + } + }; } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index f1b53edbb1cd..efed8c3c1ef4 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -851,8 +851,6 @@ public class SettingsBackupTest { Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, Settings.Secure.UI_TRANSLATION_ENABLED, - Settings.Secure.CREDENTIAL_SERVICE, - Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED, Settings.Secure.DND_CONFIGS_MIGRATED, Settings.Secure.NAVIGATION_MODE_RESTORE); diff --git a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java index 865f431183c6..3b3bf8ca15f7 100644 --- a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java +++ b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java @@ -340,6 +340,60 @@ public class SettingsValidatorsTest { failIfOffendersPresent(offenders, "Settings.Secure"); } + @Test + public void testCredentialServiceValidator_returnsTrueIfNull() { + assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(null)); + } + + @Test + public void testCredentialServiceValidator_returnsTrueIfEmpty() { + assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate("")); + } + + @Test + public void testCredentialServiceValidator_returnsTrueIfSingleComponentName() { + assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate( + "android.credentials/android.credentials.Test")); + } + + @Test + public void testCredentialServiceValidator_returnsTrueIfMultipleComponentName() { + assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate( + "android.credentials/android.credentials.Test" + + ":android.credentials/.Test2")); + } + + @Test + public void testCredentialServiceValidator_returnsFalseIfInvalidComponentName() { + assertFalse(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate("test")); + } + + @Test + public void testAutofillServiceValidator_returnsTrueIfNull() { + assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(null)); + } + + @Test + public void testAutofillServiceValidator_returnsTrueIfEmpty() { + assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("")); + } + + @Test + public void testAutofillServiceValidator_returnsTrueIfPlaceholder() { + assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("credential-provider")); + } + + @Test + public void testAutofillServiceValidator_returnsTrueIfSingleComponentName() { + assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate( + "android.credentials/android.credentials.Test")); + } + + @Test + public void testAutofillServiceValidator_returnsFalseIfInvalidComponentName() { + assertFalse(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("test")); + } + private void failIfOffendersPresent(String offenders, String settingsType) { if (offenders.length() > 0) { fail("All " + settingsType + " settings that are backed up have to have a non-null" diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 68d4b6319b2f..a745ab5cbdd9 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -140,4 +140,11 @@ flag { namespace: "systemui" description: "Move LightRevealScrim to recommended architecture" bug: "281655028" -}
\ No newline at end of file +} + +flag { + name: "media_in_scene_container" + namespace: "systemui" + description: "Enable media in the scene container framework" + bug: "296122467" +} diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp deleted file mode 100644 index 88dad6623d03..000000000000 --- a/packages/SystemUI/communal/layout/Android.bp +++ /dev/null @@ -1,35 +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 { - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -android_library { - name: "CommunalLayoutLib", - srcs: [ - "src/**/*.kt", - ], - static_libs: [ - "androidx.arch.core_core-runtime", - "androidx.compose.animation_animation-graphics", - "androidx.compose.runtime_runtime", - "androidx.compose.material3_material3", - "jsr330", - "kotlinx-coroutines-android", - "kotlinx-coroutines-core", - ], - manifest: "AndroidManifest.xml", - kotlincflags: ["-Xjvm-default=all"], -} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt deleted file mode 100644 index 91fe33cd2f4b..000000000000 --- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt +++ /dev/null @@ -1,69 +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.communal.layout - -import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard - -/** Computes the arrangement of cards. */ -class CommunalLayoutEngine { - companion object { - /** - * Determines the size that each card should be rendered in, and distributes the cards into - * columns. - * - * Returns a nested list where the outer list contains columns, and the inner list contains - * cards in each column. - * - * Currently treats the first supported size as the size to be rendered in, ignoring other - * supported sizes. - * - * Cards are ordered by priority, from highest to lowest. - */ - fun distributeCardsIntoColumns( - cards: List<CommunalGridLayoutCard>, - ): List<List<CommunalGridLayoutCardInfo>> { - val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>() - - var capacityOfLastColumn = 0 - val sorted = cards.sortedByDescending { it.priority } - for (card in sorted) { - val cardSize = card.supportedSizes.first() - if (capacityOfLastColumn >= cardSize.value) { - // Card fits in last column - capacityOfLastColumn -= cardSize.value - } else { - // Create a new column - result.add(arrayListOf()) - capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value - } - - result.last().add(CommunalGridLayoutCardInfo(card, cardSize)) - } - - return result - } - } - - /** - * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the - * card should be rendered in. - */ - data class CommunalGridLayoutCardInfo( - val card: CommunalGridLayoutCard, - val size: CommunalGridLayoutCard.Size, - ) -} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt deleted file mode 100644 index 33024f764710..000000000000 --- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt +++ /dev/null @@ -1,72 +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.communal.layout.ui.compose - -import android.util.SizeF -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.android.systemui.communal.layout.CommunalLayoutEngine -import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard -import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig - -/** - * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size - * and follows a specific order based on its priority, ensuring a seamless layout without any gaps. - */ -@Composable -fun CommunalGridLayout( - modifier: Modifier, - layoutConfig: CommunalGridLayoutConfig, - communalCards: List<CommunalGridLayoutCard>, -) { - val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards) - LazyRow( - modifier = modifier.height(layoutConfig.gridHeight), - horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter), - ) { - for (column in columns) { - item { - Column( - modifier = Modifier.width(layoutConfig.cardWidth), - verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter), - ) { - for (cardInfo in column) { - Row( - modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)), - ) { - cardInfo.card.Content( - modifier = Modifier.fillMaxSize(), - size = - SizeF( - layoutConfig.cardWidth.value, - layoutConfig.cardHeight(cardInfo.size).value, - ), - ) - } - } - } - } - } - } -} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt deleted file mode 100644 index 4b2a156c1dbd..000000000000 --- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt +++ /dev/null @@ -1,68 +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.communal.layout.ui.compose.config - -import android.util.SizeF -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -/** A card that hosts content to be rendered in the communal grid layout. */ -abstract class CommunalGridLayoutCard { - /** - * Content to be hosted by the card. - * - * To host non-Compose views, see - * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose. - * - * @param size The size given to the card. Content of the card should fill all this space, given - * that margins and paddings have been taken care of by the layout. - */ - @Composable abstract fun Content(modifier: Modifier, size: SizeF) - - /** - * Sizes supported by the card. - * - * If multiple sizes are available, they should be ranked in order of preference, from most to - * least preferred. - */ - abstract val supportedSizes: List<Size> - - /** - * Priority of the content hosted by the card. - * - * The value of priority is relative to other cards. Cards with a higher priority are generally - * ordered first. - */ - open val priority: Int = 0 - - /** - * Size of the card. - * - * @param value A numeric value that represents the size. Must be less than or equal to - * [Size.FULL]. - */ - enum class Size(val value: Int) { - /** The card takes up full height of the grid layout. */ - FULL(value = 6), - - /** The card takes up half of the vertical space of the grid layout. */ - HALF(value = 3), - - /** The card takes up a third of the vertical space of the grid layout. */ - THIRD(value = 2), - } -} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt deleted file mode 100644 index 143df838169b..000000000000 --- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt +++ /dev/null @@ -1,82 +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.communal.layout.ui.compose.config - -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.times - -/** - * Configurations of the communal grid layout. - * - * The communal grid layout follows Material Design's responsive layout grid (see - * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided - * up by columns and gutters, and each card occupies one or multiple columns. - */ -data class CommunalGridLayoutConfig( - /** - * Size in dp of each grid column. - * - * Every card occupies one or more grid columns, which means that the width of each card is - * influenced by the size of the grid columns. - */ - val gridColumnSize: Dp, - - /** - * Size in dp of each grid gutter. - * - * A gutter is the space between columns that helps separate content. This is, therefore, also - * the size of the gaps between cards, both horizontally and vertically. - */ - val gridGutter: Dp, - - /** - * Height in dp of the grid layout. - * - * Cards with a full size take up the entire height of the grid layout. - */ - val gridHeight: Dp, - - /** - * Number of grid columns that each card occupies. - * - * It's important to note that all the cards take up the same number of grid columns, or in - * simpler terms, they all have the same width. - */ - val gridColumnsPerCard: Int, -) { - /** - * Width in dp of each card. - * - * It's important to note that all the cards take up the same number of grid columns, or in - * simpler terms, they all have the same width. - */ - val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1) - - /** Returns the height of a card in dp, based on its size. */ - fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp { - return when (cardSize) { - CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1) - CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2) - CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3) - } - } - - /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */ - private fun cardHeightBy(denominator: Int): Dp { - return (gridHeight - (denominator - 1) * gridGutter) / denominator - } -} diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp deleted file mode 100644 index 9a05504cad8b..000000000000 --- a/packages/SystemUI/communal/layout/tests/Android.bp +++ /dev/null @@ -1,47 +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 { - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -android_test { - name: "CommunalLayoutLibTests", - srcs: [ - "**/*.kt", - ], - static_libs: [ - "CommunalLayoutLib", - "androidx.test.runner", - "androidx.test.rules", - "androidx.test.ext.junit", - "frameworks-base-testutils", - "junit", - "kotlinx_coroutines_test", - "mockito-target-extended-minus-junit4", - "platform-test-annotations", - "testables", - "truth", - ], - libs: [ - "android.test.mock", - "android.test.base", - "android.test.runner", - ], - jni_libs: [ - "libdexmakerjvmtiagent", - "libstaticjvmtiagent", - ], - manifest: "AndroidManifest.xml", -} diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml deleted file mode 100644 index b19007c1ff1b..000000000000 --- a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest - xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.systemui.communal.layout.tests"> - - <application android:debuggable="true" android:largeHeap="true"> - <uses-library android:name="android.test.mock" /> - <uses-library android:name="android.test.runner" /> - </application> - - <instrumentation android:name="android.testing.TestableInstrumentation" - android:targetPackage="com.android.systemui.communal.layout.tests" - android:label="Tests for CommunalLayoutLib"> - </instrumentation> - -</manifest> diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml deleted file mode 100644 index 1352b238f6fe..000000000000 --- a/packages/SystemUI/communal/layout/tests/AndroidTest.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<configuration description="Runs tests for CommunalLayoutLib"> - - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="install-arg" value="-t" /> - <option name="test-file-name" value="CommunalLayoutLibTests.apk" /> - </target_preparer> - - <option name="test-suite-tag" value="apct" /> - <option name="test-suite-tag" value="framework-base-presubmit" /> - <option name="test-tag" value="CommunalLayoutLibTests" /> - - <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="com.android.systemui.communal.layout.tests" /> - <option name="runner" value="android.testing.TestableInstrumentation" /> - <option name="hidden-api-checks" value="false"/> - </test> - -</configuration> diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt deleted file mode 100644 index 50b7c5f02068..000000000000 --- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt +++ /dev/null @@ -1,145 +0,0 @@ -package com.android.systemui.communal.layout - -import android.util.SizeF -import androidx.compose.material3.Card -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidJUnit4::class) -class CommunalLayoutEngineTest { - @Test - fun distribution_fullLayout() { - val cards = - listOf( - generateCard(CommunalGridLayoutCard.Size.FULL), - generateCard(CommunalGridLayoutCard.Size.HALF), - generateCard(CommunalGridLayoutCard.Size.HALF), - generateCard(CommunalGridLayoutCard.Size.THIRD), - generateCard(CommunalGridLayoutCard.Size.THIRD), - generateCard(CommunalGridLayoutCard.Size.THIRD), - ) - val expected = - listOf( - listOf( - CommunalGridLayoutCard.Size.FULL, - ), - listOf( - CommunalGridLayoutCard.Size.HALF, - CommunalGridLayoutCard.Size.HALF, - ), - listOf( - CommunalGridLayoutCard.Size.THIRD, - CommunalGridLayoutCard.Size.THIRD, - CommunalGridLayoutCard.Size.THIRD, - ), - ) - - assertDistributionBySize(cards, expected) - } - - @Test - fun distribution_layoutWithGaps() { - val cards = - listOf( - generateCard(CommunalGridLayoutCard.Size.HALF), - generateCard(CommunalGridLayoutCard.Size.THIRD), - generateCard(CommunalGridLayoutCard.Size.HALF), - generateCard(CommunalGridLayoutCard.Size.FULL), - generateCard(CommunalGridLayoutCard.Size.THIRD), - ) - val expected = - listOf( - listOf( - CommunalGridLayoutCard.Size.HALF, - CommunalGridLayoutCard.Size.THIRD, - ), - listOf( - CommunalGridLayoutCard.Size.HALF, - ), - listOf( - CommunalGridLayoutCard.Size.FULL, - ), - listOf( - CommunalGridLayoutCard.Size.THIRD, - ), - ) - - assertDistributionBySize(cards, expected) - } - - @Test - fun distribution_sortByPriority() { - val cards = - listOf( - generateCard(priority = 2), - generateCard(priority = 7), - generateCard(priority = 10), - generateCard(priority = 1), - generateCard(priority = 5), - ) - val expected = - listOf( - listOf(10, 7), - listOf(5, 2), - listOf(1), - ) - - assertDistributionByPriority(cards, expected) - } - - private fun assertDistributionBySize( - cards: List<CommunalGridLayoutCard>, - expected: List<List<CommunalGridLayoutCard.Size>>, - ) { - val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards) - - for (c in expected.indices) { - for (r in expected[c].indices) { - assertThat(result[c][r].size).isEqualTo(expected[c][r]) - } - } - } - - private fun assertDistributionByPriority( - cards: List<CommunalGridLayoutCard>, - expected: List<List<Int>>, - ) { - val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards) - - for (c in expected.indices) { - for (r in expected[c].indices) { - assertThat(result[c][r].card.priority).isEqualTo(expected[c][r]) - } - } - } - - private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard { - return object : CommunalGridLayoutCard() { - override val supportedSizes = listOf(size) - - @Composable - override fun Content(modifier: Modifier, size: SizeF) { - Card(modifier = modifier, content = {}) - } - } - } - - private fun generateCard(priority: Int): CommunalGridLayoutCard { - return object : CommunalGridLayoutCard() { - override val supportedSizes = listOf(Size.HALF) - override val priority = priority - - @Composable - override fun Content(modifier: Modifier, size: SizeF) { - Card(modifier = modifier, content = {}) - } - } - } -} diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt deleted file mode 100644 index 946eeecbec5d..000000000000 --- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.android.systemui.communal.layout.ui.compose.config - -import androidx.compose.ui.unit.dp -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.google.common.truth.Truth -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidJUnit4::class) -class CommunalGridLayoutConfigTest { - @Test - fun cardWidth() { - Truth.assertThat( - CommunalGridLayoutConfig( - gridColumnSize = 5.dp, - gridGutter = 3.dp, - gridHeight = 17.dp, - gridColumnsPerCard = 1, - ) - .cardWidth - ) - .isEqualTo(5.dp) - - Truth.assertThat( - CommunalGridLayoutConfig( - gridColumnSize = 5.dp, - gridGutter = 3.dp, - gridHeight = 17.dp, - gridColumnsPerCard = 2, - ) - .cardWidth - ) - .isEqualTo(13.dp) - - Truth.assertThat( - CommunalGridLayoutConfig( - gridColumnSize = 5.dp, - gridGutter = 3.dp, - gridHeight = 17.dp, - gridColumnsPerCard = 3, - ) - .cardWidth - ) - .isEqualTo(21.dp) - } - - @Test - fun cardHeight() { - val config = - CommunalGridLayoutConfig( - gridColumnSize = 5.dp, - gridGutter = 2.dp, - gridHeight = 10.dp, - gridColumnsPerCard = 3, - ) - - Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp) - Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp) - Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp) - } -} diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp index 16c24375d14f..796abf4b52d6 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -31,7 +31,6 @@ android_library { ], static_libs: [ - "CommunalLayoutLib", "SystemUI-core", "PlatformComposeCore", "PlatformComposeSceneTransitionLayout", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 7eb7dacda255..57af2ba59566 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -19,7 +19,6 @@ package com.android.systemui.bouncer.ui.composable import android.app.AlertDialog import android.app.Dialog import android.content.DialogInterface -import android.content.res.Configuration import androidx.compose.animation.Crossfade import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.snap @@ -54,8 +53,6 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -68,7 +65,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.style.TextOverflow @@ -83,8 +79,8 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.thenIf -import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel @@ -93,8 +89,8 @@ import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.common.ui.compose.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.fold.ui.composable.FoldPosture import com.android.systemui.fold.ui.composable.foldPosture +import com.android.systemui.fold.ui.helper.FoldPosture import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -149,10 +145,7 @@ private fun SceneScope.BouncerScene( ) { val backgroundColor = MaterialTheme.colorScheme.surface val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() - val layout = - calculateLayout( - isSideBySideSupported = isSideBySideSupported, - ) + val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) Box(modifier) { Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) { @@ -163,27 +156,27 @@ private fun SceneScope.BouncerScene( val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible when (layout) { - Layout.STANDARD -> + BouncerSceneLayout.STANDARD -> StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, modifier = childModifier, ) - Layout.SIDE_BY_SIDE -> + BouncerSceneLayout.SIDE_BY_SIDE -> SideBySideLayout( viewModel = viewModel, dialogFactory = dialogFactory, isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, modifier = childModifier, ) - Layout.STACKED -> + BouncerSceneLayout.STACKED -> StackedLayout( viewModel = viewModel, dialogFactory = dialogFactory, isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, modifier = childModifier, ) - Layout.SPLIT -> + BouncerSceneLayout.SPLIT -> SplitLayout( viewModel = viewModel, dialogFactory = dialogFactory, @@ -728,58 +721,10 @@ private fun StackedLayout( } } -@Composable -private fun calculateLayout( - isSideBySideSupported: Boolean, -): Layout { - val windowSizeClass = LocalWindowSizeClass.current - val width = windowSizeClass.widthSizeClass - val height = windowSizeClass.heightSizeClass - val isLarge = width > WindowWidthSizeClass.Compact && height > WindowHeightSizeClass.Compact - val isTall = - when (height) { - WindowHeightSizeClass.Expanded -> width < WindowWidthSizeClass.Expanded - WindowHeightSizeClass.Medium -> width < WindowWidthSizeClass.Medium - else -> false - } - val isSquare = - when (width) { - WindowWidthSizeClass.Compact -> height == WindowHeightSizeClass.Compact - WindowWidthSizeClass.Medium -> height == WindowHeightSizeClass.Medium - WindowWidthSizeClass.Expanded -> height == WindowHeightSizeClass.Expanded - else -> false - } - val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE - - return when { - // Small and tall devices (i.e. phone/folded in portrait) or square device not in landscape - // mode (unfolded with hinge along horizontal plane). - (!isLarge && isTall) || (isSquare && !isLandscape) -> Layout.STANDARD - // Small and wide devices (i.e. phone/folded in landscape). - !isLarge -> Layout.SPLIT - // Large and tall devices (i.e. tablet in portrait). - isTall -> Layout.STACKED - // Large and wide/square devices (i.e. tablet in landscape, unfolded). - else -> if (isSideBySideSupported) Layout.SIDE_BY_SIDE else Layout.STANDARD - } -} - interface BouncerSceneDialogFactory { operator fun invoke(): AlertDialog } -/** Enumerates all known adaptive layout configurations. */ -private enum class Layout { - /** The default UI with the bouncer laid out normally. */ - STANDARD, - /** The bouncer is displayed vertically stacked with the user switcher. */ - STACKED, - /** The bouncer is displayed side-by-side with the user switcher or an empty space. */ - SIDE_BY_SIDE, - /** The bouncer is split in two with both sides shown side-by-side. */ - SPLIT, -} - /** Enumerates all supported user-input area visibilities. */ private enum class UserInputAreaVisibility { /** diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt new file mode 100644 index 000000000000..08b7559dcf97 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt @@ -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 com.android.systemui.bouncer.ui.composable + +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.runtime.Composable +import com.android.compose.windowsizeclass.LocalWindowSizeClass +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout +import com.android.systemui.bouncer.ui.helper.SizeClass +import com.android.systemui.bouncer.ui.helper.calculateLayoutInternal + +/** + * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If + * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by + * [BouncerSceneLayout.STANDARD]. + */ +@Composable +fun calculateLayout( + isSideBySideSupported: Boolean, +): BouncerSceneLayout { + val windowSizeClass = LocalWindowSizeClass.current + + return calculateLayoutInternal( + width = windowSizeClass.widthSizeClass.toEnum(), + height = windowSizeClass.heightSizeClass.toEnum(), + isSideBySideSupported = isSideBySideSupported, + ) +} + +private fun WindowWidthSizeClass.toEnum(): SizeClass { + return when (this) { + WindowWidthSizeClass.Compact -> SizeClass.COMPACT + WindowWidthSizeClass.Medium -> SizeClass.MEDIUM + WindowWidthSizeClass.Expanded -> SizeClass.EXPANDED + else -> error("Unsupported WindowWidthSizeClass \"$this\"") + } +} + +private fun WindowHeightSizeClass.toEnum(): SizeClass { + return when (this) { + WindowHeightSizeClass.Compact -> SizeClass.COMPACT + WindowHeightSizeClass.Medium -> SizeClass.MEDIUM + WindowHeightSizeClass.Expanded -> SizeClass.EXPANDED + else -> error("Unsupported WindowHeightSizeClass \"$this\"") + } +} 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 ce84c19a53ee..2c4dc806f468 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 @@ -65,10 +65,13 @@ fun CommunalContainer( viewModel.currentScene .transform<CommunalSceneKey, SceneKey> { value -> value.toTransitionSceneKey() } .collectAsState(TransitionSceneKey.Blank) + // Don't show hub mode UI if keyguard is present. This is important since we're in the shade, + // which can be opened from many locations. + val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false) // Failsafe to hide the whole SceneTransitionLayout in case of bugginess. var showSceneTransitionLayout by remember { mutableStateOf(true) } - if (!showSceneTransitionLayout) { + if (!showSceneTransitionLayout || !isKeyguardShowing) { return } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt index 1c993cf6206c..e77ade91a93b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt @@ -23,19 +23,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext -import androidx.window.layout.FoldingFeature import androidx.window.layout.WindowInfoTracker - -sealed interface FoldPosture { - /** A foldable device that's fully closed/folded or a device that doesn't support folding. */ - data object Folded : FoldPosture - /** A foldable that's halfway open with the hinge held vertically. */ - data object Book : FoldPosture - /** A foldable that's halfway open with the hinge held horizontally. */ - data object Tabletop : FoldPosture - /** A foldable that's fully unfolded / flat. */ - data object FullyUnfolded : FoldPosture -} +import com.android.systemui.fold.ui.helper.FoldPosture +import com.android.systemui.fold.ui.helper.foldPostureInternal /** Returns the [FoldPosture] of the device currently. */ @Composable @@ -48,32 +38,6 @@ fun foldPosture(): State<FoldPosture> { initialValue = FoldPosture.Folded, key1 = layoutInfo, ) { - value = - layoutInfo - ?.displayFeatures - ?.firstNotNullOfOrNull { it as? FoldingFeature } - .let { foldingFeature -> - when (foldingFeature?.state) { - null -> FoldPosture.Folded - FoldingFeature.State.HALF_OPENED -> - foldingFeature.orientation.toHalfwayPosture() - FoldingFeature.State.FLAT -> - if (foldingFeature.isSeparating) { - // Dual screen device. - foldingFeature.orientation.toHalfwayPosture() - } else { - FoldPosture.FullyUnfolded - } - else -> error("Unsupported state \"${foldingFeature.state}\"") - } - } - } -} - -private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture { - return when (this) { - FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop - FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book - else -> error("Unsupported orientation \"$this\"") + value = foldPostureInternal(layoutInfo) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt index 28a4801d582a..765468372604 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -25,9 +25,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -36,7 +33,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -76,9 +72,6 @@ fun SceneScope.QuickSettings( .element(QuickSettings.Elements.Content) .fillMaxWidth() .defaultMinSize(minHeight = 300.dp) - .clip(RoundedCornerShape(32.dp)) - .background(MaterialTheme.colorScheme.primary) - .padding(1.dp), ) { QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, state) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 9dd7bfaf3549..871d9f9b7627 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -23,7 +23,7 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.clickable +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -51,6 +52,7 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader +import com.android.systemui.shade.ui.composable.Shade import com.android.systemui.shade.ui.composable.ShadeHeader import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager @@ -104,53 +106,59 @@ private fun SceneScope.QuickSettingsScene( ) { // TODO(b/280887232): implement the real UI. Box(modifier = modifier.fillMaxSize()) { - val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() - val collapsedHeaderHeight = - with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - Modifier.fillMaxSize() - .clickable(onClick = { viewModel.onContentClicked() }) - .padding(start = 16.dp, end = 16.dp, bottom = 48.dp) - ) { - when (LocalWindowSizeClass.current.widthSizeClass) { - WindowWidthSizeClass.Compact -> - AnimatedVisibility( - visible = !isCustomizing, - enter = - expandVertically( - animationSpec = tween(1000), - initialHeight = { collapsedHeaderHeight }, - ) + fadeIn(tween(1000)), - exit = - shrinkVertically( - animationSpec = tween(1000), - targetHeight = { collapsedHeaderHeight }, - shrinkTowards = Alignment.Top, - ) + fadeOut(tween(1000)), - ) { - ExpandedShadeHeader( + Box(modifier = Modifier.fillMaxSize()) { + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() + val collapsedHeaderHeight = + with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } + Spacer( + modifier = + Modifier.element(Shade.Elements.ScrimBackground) + .fillMaxSize() + .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim) + ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = + Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 48.dp) + ) { + when (LocalWindowSizeClass.current.widthSizeClass) { + WindowWidthSizeClass.Compact -> + AnimatedVisibility( + visible = !isCustomizing, + enter = + expandVertically( + animationSpec = tween(1000), + initialHeight = { collapsedHeaderHeight }, + ) + fadeIn(tween(1000)), + exit = + shrinkVertically( + animationSpec = tween(1000), + targetHeight = { collapsedHeaderHeight }, + shrinkTowards = Alignment.Top, + ) + fadeOut(tween(1000)), + ) { + ExpandedShadeHeader( + viewModel = viewModel.shadeHeaderViewModel, + createTintedIconManager = createTintedIconManager, + createBatteryMeterViewController = createBatteryMeterViewController, + statusBarIconController = statusBarIconController, + ) + } + else -> + CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, ) - } - else -> - CollapsedShadeHeader( - viewModel = viewModel.shadeHeaderViewModel, - createTintedIconManager = createTintedIconManager, - createBatteryMeterViewController = createBatteryMeterViewController, - statusBarIconController = statusBarIconController, - ) + } + Spacer(modifier = Modifier.height(16.dp)) + QuickSettings( + modifier = Modifier.fillMaxHeight(), + viewModel.qsSceneAdapter, + QSSceneAdapter.State.QS + ) } - Spacer(modifier = Modifier.height(16.dp)) - QuickSettings( - modifier = Modifier.fillMaxHeight(), - viewModel.qsSceneAdapter, - QSSceneAdapter.State.QS - ) } HeadsUpNotificationSpace( viewModel = viewModel.notifications, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt new file mode 100644 index 000000000000..61b205710873 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.fold.ui.helper + +import android.graphics.Rect +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.window.layout.FoldingFeature +import androidx.window.layout.WindowLayoutInfo +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class FoldPostureTest : SysuiTestCase() { + + @Test + fun foldPosture_whenNull_returnsFolded() { + assertThat(foldPostureInternal(null)).isEqualTo(FoldPosture.Folded) + } + + @Test + fun foldPosture_whenHalfOpenHorizontally_returnsTabletop() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.HALF_OPENED, + orientation = FoldingFeature.Orientation.HORIZONTAL, + ) + ) + ) + .isEqualTo(FoldPosture.Tabletop) + } + + @Test + fun foldPosture_whenHalfOpenVertically_returnsBook() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.HALF_OPENED, + orientation = FoldingFeature.Orientation.VERTICAL, + ) + ) + ) + .isEqualTo(FoldPosture.Book) + } + + @Test + fun foldPosture_whenFlatAndNotSeparating_returnsFullyUnfolded() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.FLAT, + orientation = FoldingFeature.Orientation.HORIZONTAL, + isSeparating = false, + ) + ) + ) + .isEqualTo(FoldPosture.FullyUnfolded) + } + + @Test + fun foldPosture_whenFlatAndSeparatingHorizontally_returnsTabletop() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.FLAT, + isSeparating = true, + orientation = FoldingFeature.Orientation.HORIZONTAL, + ) + ) + ) + .isEqualTo(FoldPosture.Tabletop) + } + + @Test + fun foldPosture_whenFlatAndSeparatingVertically_returnsBook() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.FLAT, + isSeparating = true, + orientation = FoldingFeature.Orientation.VERTICAL, + ) + ) + ) + .isEqualTo(FoldPosture.Book) + } + + private fun createWindowLayoutInfo( + state: FoldingFeature.State, + orientation: FoldingFeature.Orientation = FoldingFeature.Orientation.VERTICAL, + isSeparating: Boolean = false, + occlusionType: FoldingFeature.OcclusionType = FoldingFeature.OcclusionType.NONE, + ): WindowLayoutInfo { + return WindowLayoutInfo( + listOf( + object : FoldingFeature { + override val bounds: Rect = Rect(0, 0, 100, 100) + override val isSeparating: Boolean = isSeparating + override val occlusionType: FoldingFeature.OcclusionType = occlusionType + override val orientation: FoldingFeature.Orientation = orientation + override val state: FoldingFeature.State = state + } + ) + ) + } +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index f0e3c99b007d..643420989d1a 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -133,16 +133,6 @@ public interface ActivityStarter { boolean afterKeyguardGone, boolean deferred); - /** Execute a runnable after dismissing keyguard. */ - void executeRunnableDismissingKeyguard( - Runnable runnable, - Runnable cancelAction, - boolean dismissShade, - boolean afterKeyguardGone, - boolean deferred, - boolean willAnimateOnKeyguard, - @Nullable String customMessage); - /** Whether we should animate an activity launch. */ boolean shouldAnimateLaunch(boolean isActivityIntent); diff --git a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml index 952f056b3023..cc99f5e125f3 100644 --- a/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml +++ b/packages/SystemUI/res-keyguard/layout/shade_carrier_new.xml @@ -22,13 +22,15 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" - android:orientation="horizontal" > + android:orientation="horizontal" + android:theme="@style/Theme.SystemUI.QuickSettings.Header" > <com.android.systemui.util.AutoMarqueeTextView android:id="@+id/mobile_carrier_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" + android:textAppearance="@style/TextAppearance.QS.Status.Carriers" android:layout_marginEnd="@dimen/qs_carrier_margin_width" android:visibility="gone" android:textDirection="locale" diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml index 355e75d0716b..9c08f5ef4cfe 100644 --- a/packages/SystemUI/res/drawable/notification_material_bg.xml +++ b/packages/SystemUI/res/drawable/notification_material_bg.xml @@ -18,7 +18,7 @@ <layer-list xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:color="?android:attr/colorControlHighlight"> - <item> + <item android:id="@+id/notification_background_color_layer"> <shape> <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" /> </shape> diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index b8f4c0f212c3..7ab44e70e6fe 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -53,6 +53,8 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical" + android:focusable="true" + android:importantForAccessibility="no" android:tint="?attr/shadeActive" android:visibility="gone" /> diff --git a/packages/SystemUI/res/layout/udfps_touch_overlay.xml b/packages/SystemUI/res/layout/udfps_touch_overlay.xml new file mode 100644 index 000000000000..ea92776aba2d --- /dev/null +++ b/packages/SystemUI/res/layout/udfps_touch_overlay.xml @@ -0,0 +1,22 @@ +<?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. + --> +<com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/udfps_touch_overlay" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@string/accessibility_fingerprint_label"> +</com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay> diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt index 375727437b8b..1ee58deb501c 100644 --- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt +++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt @@ -317,7 +317,7 @@ class ActiveUnlockConfig @Inject constructor( } keyguardUpdateMonitor?.let { - val anyFaceEnrolled = it.isFaceEnrolled + val anyFaceEnrolled = it.isFaceEnabledAndEnrolled val anyFingerprintEnrolled = it.isUnlockWithFingerprintPossible( selectedUserInteractor.getSelectedUserId()) val udfpsEnrolled = it.isUdfpsEnrolled @@ -372,7 +372,7 @@ class ActiveUnlockConfig @Inject constructor( keyguardUpdateMonitor?.let { pw.println(" shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" + "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}") - pw.println(" faceEnrolled=${it.isFaceEnrolled}") + pw.println(" isFaceEnabledAndEnrolled=${it.isFaceEnabledAndEnrolled}") pw.println(" fpUnlockPossible=${ it.isUnlockWithFingerprintPossible(selectedUserInteractor.getSelectedUserId())}") pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}") diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 1fa55f5d839b..54cb501db002 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -384,6 +384,11 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } } + @Nullable + View getAodNotifIconContainer() { + return mAodIconContainer; + } + @Override protected void onViewDetached() { mClockRegistry.unregisterClockChangeListener(mClockChangedListener); @@ -639,6 +644,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } } else { mNotificationIconAreaController.setupAodIcons(nic); + mAodIconContainer = nic; } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt deleted file mode 100644 index d7019b5c5d04..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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. - */ - -package com.android.keyguard - -import android.annotation.CurrentTimeMillisLong -import com.android.systemui.common.buffer.RingBuffer -import com.android.systemui.dump.DumpsysTableLogger -import com.android.systemui.dump.Row - -/** Verbose debug information associated. */ -data class KeyguardFaceListenModel( - @CurrentTimeMillisLong override var timeMillis: Long = 0L, - override var userId: Int = 0, - override var listening: Boolean = false, - // keep sorted - var allowedDisplayStateWhileAwake: Boolean = false, - var alternateBouncerShowing: Boolean = false, - var authInterruptActive: Boolean = false, - var biometricSettingEnabledForUser: Boolean = false, - var bouncerFullyShown: Boolean = false, - var faceAndFpNotAuthenticated: Boolean = false, - var faceAuthAllowed: Boolean = false, - var faceDisabled: Boolean = false, - var faceLockedOut: Boolean = false, - var goingToSleep: Boolean = false, - var keyguardAwake: Boolean = false, - var keyguardGoingAway: Boolean = false, - var listeningForFaceAssistant: Boolean = false, - var occludingAppRequestingFaceAuth: Boolean = false, - var postureAllowsListening: Boolean = false, - var secureCameraLaunched: Boolean = false, - var supportsDetect: Boolean = false, - var switchingUser: Boolean = false, - var systemUser: Boolean = false, - var udfpsFingerDown: Boolean = false, - var userNotTrustedOrDetectionIsNeeded: Boolean = false, -) : KeyguardListenModel() { - - /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */ - val asStringList: List<String> by lazy { - listOf( - DATE_FORMAT.format(timeMillis), - timeMillis.toString(), - userId.toString(), - listening.toString(), - // keep sorted - allowedDisplayStateWhileAwake.toString(), - alternateBouncerShowing.toString(), - authInterruptActive.toString(), - biometricSettingEnabledForUser.toString(), - bouncerFullyShown.toString(), - faceAndFpNotAuthenticated.toString(), - faceAuthAllowed.toString(), - faceDisabled.toString(), - faceLockedOut.toString(), - goingToSleep.toString(), - keyguardAwake.toString(), - keyguardGoingAway.toString(), - listeningForFaceAssistant.toString(), - occludingAppRequestingFaceAuth.toString(), - postureAllowsListening.toString(), - secureCameraLaunched.toString(), - supportsDetect.toString(), - switchingUser.toString(), - systemUser.toString(), - udfpsFingerDown.toString(), - userNotTrustedOrDetectionIsNeeded.toString(), - ) - } - - /** - * [RingBuffer] to store [KeyguardFaceListenModel]. After the buffer is full, it will recycle - * old events. - * - * Do not use [append] to add new elements. Instead use [insert], as it will recycle if - * necessary. - */ - class Buffer { - private val buffer = RingBuffer(CAPACITY) { KeyguardFaceListenModel() } - - fun insert(model: KeyguardFaceListenModel) { - buffer.advance().apply { - timeMillis = model.timeMillis - userId = model.userId - listening = model.listening - // keep sorted - allowedDisplayStateWhileAwake = model.allowedDisplayStateWhileAwake - alternateBouncerShowing = model.alternateBouncerShowing - authInterruptActive = model.authInterruptActive - biometricSettingEnabledForUser = model.biometricSettingEnabledForUser - bouncerFullyShown = model.bouncerFullyShown - faceAndFpNotAuthenticated = model.faceAndFpNotAuthenticated - faceAuthAllowed = model.faceAuthAllowed - faceDisabled = model.faceDisabled - faceLockedOut = model.faceLockedOut - goingToSleep = model.goingToSleep - keyguardAwake = model.keyguardAwake - keyguardGoingAway = model.keyguardGoingAway - listeningForFaceAssistant = model.listeningForFaceAssistant - occludingAppRequestingFaceAuth = model.occludingAppRequestingFaceAuth - postureAllowsListening = model.postureAllowsListening - secureCameraLaunched = model.secureCameraLaunched - supportsDetect = model.supportsDetect - switchingUser = model.switchingUser - systemUser = model.systemUser - udfpsFingerDown = model.udfpsFingerDown - userNotTrustedOrDetectionIsNeeded = model.userNotTrustedOrDetectionIsNeeded - } - } - /** - * Returns the content of the buffer (sorted from latest to newest). - * - * @see KeyguardFingerprintListenModel.asStringList - */ - fun toList(): List<Row> { - return buffer.asSequence().map { it.asStringList }.toList() - } - } - - companion object { - const val CAPACITY = 40 // number of logs to retain - - /** Headers for dumping a table using [DumpsysTableLogger]. */ - @JvmField - val TABLE_HEADERS = - listOf( - "timestamp", - "time_millis", - "userId", - "listening", - // keep sorted - "allowedDisplayStateWhileAwake", - "alternateBouncerShowing", - "authInterruptActive", - "biometricSettingEnabledForUser", - "bouncerFullyShown", - "faceAndFpNotAuthenticated", - "faceAuthAllowed", - "faceDisabled", - "faceLockedOut", - "goingToSleep", - "keyguardAwake", - "keyguardGoingAway", - "listeningForFaceAssistant", - "occludingAppRequestingFaceAuth", - "postureAllowsListening", - "secureCameraLaunched", - "supportsDetect", - "switchingUser", - "systemUser", - "udfpsFingerDown", - "userNotTrustedOrDetectionIsNeeded", - ) - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 1b6112f52082..f706301df1ca 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -214,7 +214,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void onUserInput() { mBouncerMessageInteractor.onPrimaryBouncerUserInput(); mKeyguardFaceAuthInteractor.onPrimaryBouncerUserInput(); - mUpdateMonitor.cancelFaceAuth(); } @Override @@ -340,16 +339,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final SwipeListener mSwipeListener = new SwipeListener() { @Override public void onSwipeUp() { - if (!mUpdateMonitor.isFaceDetectionRunning()) { - mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer(); - boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth( - FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER); + if (mKeyguardFaceAuthInteractor.canFaceAuthRun()) { mKeyguardSecurityCallback.userActivity(); - if (didFaceAuthRun) { - showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true); - } } - if (mUpdateMonitor.isFaceEnrolled()) { + mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer(); + if (mKeyguardFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()) { mUpdateMonitor.requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT, "swipeUpOnBouncer"); @@ -755,7 +749,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } mView.onResume( mSecurityModel.getSecurityMode(mSelectedUserInteractor.getSelectedUserId()), - mKeyguardStateController.isFaceEnrolled()); + mKeyguardStateController.isFaceEnrolledAndEnabled()); } /** Sets an initial message that would override the default message */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 87d937bc45fb..4fbf077a8852 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -84,6 +84,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV Dumpable { private static final boolean DEBUG = KeyguardConstants.DEBUG; @VisibleForTesting static final String TAG = "KeyguardStatusViewController"; + private static final long STATUS_AREA_HEIGHT_ANIMATION_MILLIS = 133; /** * Duration to use for the animator when the keyguard status view alignment changes, and a @@ -104,6 +105,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardInteractor mKeyguardInteractor; private final PowerInteractor mPowerInteractor; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; + private final DozeParameters mDozeParameters; + + private View mStatusArea = null; + private ValueAnimator mStatusAreaHeightAnimator = null; private Boolean mSplitShadeEnabled = false; private Boolean mStatusViewCentered = true; @@ -123,6 +128,46 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } }; + private final View.OnLayoutChangeListener mStatusAreaLayoutChangeListener = + new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, + int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom + ) { + if (!mDozeParameters.getAlwaysOn()) { + return; + } + + int oldHeight = oldBottom - oldTop; + int diff = v.getHeight() - oldHeight; + if (diff == 0) { + return; + } + + int startValue = -1 * diff; + long duration = STATUS_AREA_HEIGHT_ANIMATION_MILLIS; + if (mStatusAreaHeightAnimator != null + && mStatusAreaHeightAnimator.isRunning()) { + duration += mStatusAreaHeightAnimator.getDuration() + - mStatusAreaHeightAnimator.getCurrentPlayTime(); + startValue += (int) mStatusAreaHeightAnimator.getAnimatedValue(); + mStatusAreaHeightAnimator.cancel(); + mStatusAreaHeightAnimator = null; + } + + mStatusAreaHeightAnimator = ValueAnimator.ofInt(startValue, 0); + mStatusAreaHeightAnimator.setDuration(duration); + final View nic = mKeyguardClockSwitchController.getAodNotifIconContainer(); + if (nic != null) { + mStatusAreaHeightAnimator.addUpdateListener(anim -> { + nic.setTranslationY((int) anim.getAnimatedValue()); + }); + } + mStatusAreaHeightAnimator.start(); + } + }; + @Inject public KeyguardStatusViewController( KeyguardStatusView keyguardStatusView, @@ -144,6 +189,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mKeyguardClockSwitchController = keyguardClockSwitchController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mConfigurationController = configurationController; + mDozeParameters = dozeParameters; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true, logger.getBuffer()); @@ -218,12 +264,15 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV @Override protected void onViewAttached() { + mStatusArea = mView.findViewById(R.id.keyguard_status_area); + mStatusArea.addOnLayoutChangeListener(mStatusAreaLayoutChangeListener); mKeyguardUpdateMonitor.registerCallback(mInfoCallback); mConfigurationController.addCallback(mConfigurationListener); } @Override protected void onViewDetached() { + mStatusArea.removeOnLayoutChangeListener(mStatusAreaLayoutChangeListener); mKeyguardUpdateMonitor.removeCallback(mInfoCallback); mConfigurationController.removeCallback(mConfigurationListener); } @@ -293,9 +342,15 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV /** * Get the height of the keyguard status view without the notification icon area, as that's * only visible on AOD. + * + * We internally animate height changes to the status area to prevent discontinuities in the + * doze animation introduced by the height suddenly changing due to smartpace. */ public int getLockscreenHeight() { - return mView.getHeight() - mKeyguardClockSwitchController.getNotificationIconAreaHeight(); + int heightAnimValue = mStatusAreaHeightAnimator == null ? 0 : + (int) mStatusAreaHeightAnimator.getAnimatedValue(); + return mView.getHeight() + heightAnimValue + - mKeyguardClockSwitchController.getNotificationIconAreaHeight(); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index baab637a979c..c5bb0995f492 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -34,48 +34,11 @@ import static android.hardware.biometrics.BiometricSourceType.FACE; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT; -import static android.os.PowerManager.WAKE_REASON_UNKNOWN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; -import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_DISPLAY_OFF; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FP_LOCKED_OUT; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_TRUST_ENABLED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_DURING_CANCELLATION; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_TRUST_DISABLED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_CAMERA_LAUNCHED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_FP_AUTHENTICATED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_GOING_TO_SLEEP; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_RESET; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_KEYGUARD_INIT; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_POSTURE_CHANGED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED; -import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING; -import static com.android.systemui.DejankUtils.whitelistIpcs; -import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; @@ -104,10 +67,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.SensorProperties; import android.hardware.biometrics.SensorPropertiesInternal; -import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceManager; -import android.hardware.face.FaceSensorPropertiesInternal; -import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; @@ -137,7 +97,6 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.SparseArray; import android.util.SparseBooleanArray; -import android.view.Display; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -161,7 +120,6 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; -import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent; @@ -172,12 +130,10 @@ import com.android.systemui.keyguard.shared.model.FaceDetectionStatus; import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus; import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus; import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus; -import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.WeatherData; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; -import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -318,7 +274,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final boolean mIsSystemUser; private final AuthController mAuthController; private final UiEventLogger mUiEventLogger; - private final Set<Integer> mFaceAcquiredInfoIgnoreList; private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage; private final PackageManager mPackageManager; private int mStatusBarState; @@ -339,26 +294,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } }; - private final DisplayTracker.Callback mDisplayCallback = new DisplayTracker.Callback() { - @Override - public void onDisplayChanged(int displayId) { - if (displayId != Display.DEFAULT_DISPLAY) { - return; - } - - if (mWakefulness.getWakefulness() == WAKEFULNESS_AWAKE - && mDisplayTracker.getDisplay(mDisplayTracker.getDefaultDisplayId()).getState() - == Display.STATE_OFF) { - mAllowedDisplayStateWhileAwakeForFaceAuth = false; - updateFaceListeningState( - BIOMETRIC_ACTION_STOP, - FACE_AUTH_DISPLAY_OFF - ); - } else { - mAllowedDisplayStateWhileAwakeForFaceAuth = true; - } - } - }; private final FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig; HashMap<Integer, SimData> mSimDatas = new HashMap<>(); @@ -377,9 +312,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mNeedsSlowUnlockTransition; private boolean mAssistantVisible; private boolean mOccludingAppRequestingFp; - private boolean mOccludingAppRequestingFace; private boolean mSecureCameraLaunched; - private boolean mAllowedDisplayStateWhileAwakeForFaceAuth = true; private boolean mBiometricPromptShowing; @VisibleForTesting protected boolean mTelephonyCapable; @@ -409,7 +342,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final TrustManager mTrustManager; private final UserManager mUserManager; private final DevicePolicyManager mDevicePolicyManager; - private final DevicePostureController mPostureController; private final BroadcastDispatcher mBroadcastDispatcher; private final InteractionJankMonitor mInteractionJankMonitor; private final LatencyTracker mLatencyTracker; @@ -422,13 +354,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Nullable private final FingerprintManager mFpm; @Nullable - private final FaceManager mFaceManager; - @Nullable private KeyguardFaceAuthInteractor mFaceAuthInteractor; private final TaskStackChangeListeners mTaskStackChangeListeners; private final IActivityTaskManager mActivityTaskManager; - private final WakefulnessLifecycle mWakefulness; - private final DisplayTracker mDisplayTracker; private final SelectedUserInteractor mSelectedUserInteractor; private final LockPatternUtils mLockPatternUtils; @VisibleForTesting @@ -439,11 +367,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private List<SubscriptionInfo> mSubscriptionInfo; @VisibleForTesting protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; - private int mFaceRunningState = BIOMETRIC_STATE_STOPPED; private boolean mIsDreaming; private boolean mLogoutEnabled; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private int mPostureState = DEVICE_POSTURE_UNKNOWN; private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider; /** @@ -455,7 +381,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // If the HAL dies or is unable to authenticate, keyguard should retry after a short delay private int mHardwareFingerprintUnavailableRetryCount = 0; - private int mHardwareFaceUnavailableRetryCount = 0; private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms private static final int HAL_ERROR_RETRY_MAX = 20; @@ -465,7 +390,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting protected final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived; - private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived; private final Provider<SessionTracker> mSessionTrackerProvider; @VisibleForTesting @@ -481,8 +405,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void onChanged(boolean enabled, int userId) { mHandler.post(() -> { mBiometricEnabledForUser.put(userId, enabled); - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); }); } }; @@ -525,15 +448,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final KeyguardFingerprintListenModel.Buffer mFingerprintListenBuffer = new KeyguardFingerprintListenModel.Buffer(); - private final KeyguardFaceListenModel.Buffer mFaceListenBuffer = - new KeyguardFaceListenModel.Buffer(); private final KeyguardActiveUnlockModel.Buffer mActiveUnlockTriggerBuffer = new KeyguardActiveUnlockModel.Buffer(); @VisibleForTesting SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>(); - @VisibleForTesting - SparseArray<BiometricAuthenticated> mUserFaceAuthenticated = new SparseArray<>(); private static int sCurrentUser; @@ -561,11 +480,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // authenticating. TrustManager sends an onTrustChanged whenever a user unlocks keyguard, // for this reason we need to make sure to not authenticate. if (wasTrusted == enabled || enabled) { - updateBiometricListeningState(BIOMETRIC_ACTION_STOP, - FACE_AUTH_STOPPED_TRUST_ENABLED); + updateFingerprintListeningState(BIOMETRIC_ACTION_STOP); } else { - updateBiometricListeningState(BIOMETRIC_ACTION_START, - FACE_AUTH_TRIGGERED_TRUST_DISABLED); + updateFingerprintListeningState(BIOMETRIC_ACTION_START); } mLogger.logTrustChanged(wasTrusted, enabled, userId); @@ -807,8 +724,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void setKeyguardGoingAway(boolean goingAway) { mKeyguardGoingAway = goingAway; if (mKeyguardGoingAway) { - updateFaceListeningState(BIOMETRIC_ACTION_STOP, - FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -855,29 +770,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - if (occlusionChanged) { - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED); - } else if (showingChanged) { - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED); + if (occlusionChanged || showingChanged) { + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } } /** - * Request to listen for face authentication when an app is occluding keyguard. - * - * @param request if true and mKeyguardOccluded, request face auth listening, else default - * to normal behavior. - * See {@link KeyguardUpdateMonitor#shouldListenForFace()} - */ - public void requestFaceAuthOnOccludingApp(boolean request) { - mOccludingAppRequestingFace = request; - int action = mOccludingAppRequestingFace ? BIOMETRIC_ACTION_UPDATE : BIOMETRIC_ACTION_STOP; - updateFaceListeningState(action, FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED); - } - - /** * Request to listen for fingerprint when an app is occluding keyguard. * * @param request if true and mKeyguardOccluded, request fingerprint listening, else default @@ -894,8 +792,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void onCameraLaunched() { mSecureCameraLaunched = true; - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_CAMERA_LAUNCHED); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } /** @@ -951,8 +848,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // Don't send cancel if authentication succeeds mFingerprintCancelSignal = null; mLogger.logFingerprintSuccess(userId, isStrongBiometric); - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_FP_AUTHENTICATED); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -1025,7 +921,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.logFingerprintDetected(authUserId, isStrongBiometric); } else if (biometricSourceType == FACE) { mLogger.logFaceDetected(authUserId, isStrongBiometric); - setFaceRunningState(BIOMETRIC_STATE_STOPPED); } Trace.endSection(); @@ -1140,7 +1035,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (isUdfpsEnrolled()) { updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } - stopListeningForFace(FACE_AUTH_STOPPED_FP_LOCKED_OUT); } mLogger.logFingerprintError(msgId, errString); @@ -1218,16 +1112,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void onFaceAuthenticated(int userId, boolean isStrongBiometric) { Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated"); Assert.isMainThread(); - mUserFaceAuthenticated.put(userId, - new BiometricAuthenticated(true, isStrongBiometric)); // Update/refresh trust state only if user can skip bouncer if (getUserCanSkipBouncer(userId)) { mTrustManager.unlockedByBiometricForUser(userId, FACE); } - // Don't send cancel if authentication succeeds - mFaceCancelSignal = null; - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); mLogger.d("onFaceAuthenticated"); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -1247,11 +1136,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.endSection(); } - /** - * @deprecated This is being migrated to use modern architecture, this method is visible purely - * for bridging the gap while the migration is active. - */ - @Deprecated private void handleFaceAuthFailed() { Assert.isMainThread(); String reason = @@ -1264,8 +1148,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab "faceFailure-" + reason); mLogger.d("onFaceAuthFailed"); - mFaceCancelSignal = null; - setFaceRunningState(BIOMETRIC_STATE_STOPPED); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -1276,11 +1158,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mContext.getString(R.string.kg_face_not_recognized)); } - /** - * @deprecated This is being migrated to use modern architecture, this method is visible purely - * for bridging the gap while the migration is active. - */ - @Deprecated private void handleFaceAcquired(int acquireInfo) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1298,44 +1175,23 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - /** - * @deprecated This is being migrated to use modern architecture, this method is visible purely - * for bridging the gap while the migration is active. - */ - @Deprecated private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) { Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated"); - try { - if (mGoingToSleep) { - mLogger.d("Aborted successful auth because device is going to sleep."); - return; - } - final int userId = mSelectedUserInteractor.getSelectedUserId(true); - if (userId != authUserId) { - mLogger.logFaceAuthForWrongUser(authUserId); - return; - } - if (!isFaceAuthInteractorEnabled() && isFaceDisabled(userId)) { - mLogger.logFaceAuthDisabledForUser(userId); - return; - } - mLogger.logFaceAuthSuccess(userId); - onFaceAuthenticated(userId, isStrongBiometric); - } finally { - setFaceRunningState(BIOMETRIC_STATE_STOPPED); + if (mGoingToSleep) { + mLogger.d("Aborted successful auth because device is going to sleep."); + return; } + final int userId = mSelectedUserInteractor.getSelectedUserId(true); + if (userId != authUserId) { + mLogger.logFaceAuthForWrongUser(authUserId); + return; + } + mLogger.logFaceAuthSuccess(userId); + onFaceAuthenticated(userId, isStrongBiometric); Trace.endSection(); } - /** - * @deprecated This is being migrated to use modern architecture, this method is visible purely - * for bridging the gap while the migration is active. - */ - @Deprecated private void handleFaceHelp(int msgId, String helpString) { - if (mFaceAcquiredInfoIgnoreList.contains(msgId)) { - return; - } Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -1345,49 +1201,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - /** - * @deprecated This is being migrated to use modern architecture, this method is visible purely - * for bridging the gap while the migration is active. - */ - @Deprecated private void handleFaceError(int msgId, final String originalErrMsg) { Assert.isMainThread(); String errString = originalErrMsg; mLogger.logFaceAuthError(msgId, originalErrMsg); - if (mHandler.hasCallbacks(mFaceCancelNotReceived)) { - mHandler.removeCallbacks(mFaceCancelNotReceived); - } // Error is always the end of authentication lifecycle - mFaceCancelSignal = null; boolean cameraPrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled( SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, SensorPrivacyManager.Sensors.CAMERA); - if (msgId == FaceManager.FACE_ERROR_CANCELED - && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) { - setFaceRunningState(BIOMETRIC_STATE_STOPPED); - updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_TRIGGERED_DURING_CANCELLATION); - } else { - setFaceRunningState(BIOMETRIC_STATE_STOPPED); - } - final boolean isHwUnavailable = msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE; - if (isHwUnavailable - || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) { - if (mHardwareFaceUnavailableRetryCount < HAL_ERROR_RETRY_MAX) { - mHardwareFaceUnavailableRetryCount++; - mHandler.removeCallbacks(mRetryFaceAuthentication); - mHandler.postDelayed(mRetryFaceAuthentication, HAL_ERROR_RETRY_TIMEOUT); - } - } - - boolean lockedOutStateChanged = false; if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) { - lockedOutStateChanged = !mFaceLockedOutPermanent; - mFaceLockedOutPermanent = true; - if (isFaceClass3()) { + if (getFaceAuthInteractor() != null && getFaceAuthInteractor().isFaceAuthStrong()) { updateFingerprintListeningState(BIOMETRIC_ACTION_STOP); } } @@ -1404,10 +1230,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - if (lockedOutStateChanged) { - notifyLockedOutStateChanged(FACE); - } - if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(msgId)) { requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL, @@ -1415,49 +1237,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - private final Runnable mRetryFaceAuthentication = new Runnable() { - @Override - public void run() { - mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount); - updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE); - } - }; - - private void onFaceCancelNotReceived() { - mLogger.e("Face cancellation not received, transitioning to STOPPED"); - mFaceRunningState = BIOMETRIC_STATE_STOPPED; - KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP, - FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED); - } - - private void handleFaceLockoutReset(@LockoutMode int mode) { - mLogger.logFaceLockoutReset(mode); - final boolean wasLockoutPermanent = mFaceLockedOutPermanent; - mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT); - final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent); - - mHandler.postDelayed(() -> updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay()); - - if (changed) { - notifyLockedOutStateChanged(FACE); - } - } - - private void setFaceRunningState(int faceRunningState) { - boolean wasRunning = mFaceRunningState == BIOMETRIC_STATE_RUNNING; - boolean isRunning = faceRunningState == BIOMETRIC_STATE_RUNNING; - mFaceRunningState = faceRunningState; - mLogger.logFaceRunningState(mFaceRunningState); - // Clients of KeyguardUpdateMonitor don't care about the internal state or about the - // asynchronousness of the cancel cycle. So only notify them if the actually running state - // has changed. - if (wasRunning != isRunning) { - notifyFaceRunningStateChanged(); - } - } - private void notifyFaceRunningStateChanged() { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1478,14 +1257,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ @Deprecated public boolean isFaceDetectionRunning() { - if (isFaceAuthInteractorEnabled()) { - return getFaceAuthInteractor().isRunning(); - } - return mFaceRunningState == BIOMETRIC_STATE_RUNNING; - } - - private boolean isFaceAuthInteractorEnabled() { - return mFaceAuthInteractor != null && mFaceAuthInteractor.isEnabled(); + return getFaceAuthInteractor() != null && getFaceAuthInteractor().isRunning(); } private @Nullable KeyguardFaceAuthInteractor getFaceAuthInteractor() { @@ -1495,14 +1267,32 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * Set the face auth interactor that should be used for initiating face authentication. */ - public void setFaceAuthInteractor(@Nullable KeyguardFaceAuthInteractor faceAuthInteractor) { + public void setFaceAuthInteractor(KeyguardFaceAuthInteractor faceAuthInteractor) { + if (mFaceAuthInteractor != null) { + mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener); + } mFaceAuthInteractor = faceAuthInteractor; mFaceAuthInteractor.registerListener(mFaceAuthenticationListener); } - private FaceAuthenticationListener mFaceAuthenticationListener = + private final FaceAuthenticationListener mFaceAuthenticationListener = new FaceAuthenticationListener() { @Override + public void onAuthEnrollmentStateChanged(boolean enrolled) { + notifyAboutEnrollmentChange(TYPE_FACE); + } + + @Override + public void onRunningStateChanged(boolean isRunning) { + notifyFaceRunningStateChanged(); + } + + @Override + public void onLockoutStateChanged(boolean isLockedOut) { + notifyLockedOutStateChanged(FACE); + } + + @Override public void onAuthenticationStatusChanged( @NonNull FaceAuthenticationStatus status ) { @@ -1546,32 +1336,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** - * @deprecated This method is not needed anymore with the new face auth system. - */ - @Deprecated - private boolean isFaceDisabled(int userId) { - // TODO(b/140035044) - return whitelistIpcs(() -> - (mDevicePolicyManager.getKeyguardDisabledFeatures(null, userId) - & DevicePolicyManager.KEYGUARD_DISABLE_FACE) != 0 - || isSimPinSecure()); - } - - /** * @return whether the current user has been authenticated with face. This may be true * on the lockscreen if the user doesn't have bypass enabled. * - * @deprecated This is being migrated to use modern architecture. + * @deprecated Use {@link KeyguardFaceAuthInteractor#isAuthenticated()} */ @Deprecated public boolean getIsFaceAuthenticated() { - boolean faceAuthenticated = false; - BiometricAuthenticated bioFaceAuthenticated = - mUserFaceAuthenticated.get(mSelectedUserInteractor.getSelectedUserId()); - if (bioFaceAuthenticated != null) { - faceAuthenticated = bioFaceAuthenticated.mAuthenticated; - } - return faceAuthenticated; + return getFaceAuthInteractor() != null && getFaceAuthInteractor().isAuthenticated(); } public boolean getUserCanSkipBouncer(int userId) { @@ -1590,17 +1362,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId); boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric); - return fingerprintAllowed || getUserUnlockedWithFace(userId); + boolean unlockedByFace = isCurrentUserUnlockedWithFace() && isUnlockingWithBiometricAllowed( + FACE); + return fingerprintAllowed || unlockedByFace; } /** * Returns whether the user is unlocked with face. + * @deprecated Use {@link KeyguardFaceAuthInteractor#isAuthenticated()} instead */ - public boolean getUserUnlockedWithFace(int userId) { - BiometricAuthenticated face = mUserFaceAuthenticated.get(userId); - return face != null && face.mAuthenticated - && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric); + @Deprecated + public boolean isCurrentUserUnlockedWithFace() { + return getFaceAuthInteractor() != null && getFaceAuthInteractor().isAuthenticated(); } /** @@ -1609,13 +1383,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public boolean getUserUnlockedWithBiometricAndIsBypassing(int userId) { BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId); - BiometricAuthenticated face = mUserFaceAuthenticated.get(userId); // fingerprint always bypasses boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric); - boolean faceAllowed = face != null && face.mAuthenticated - && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric); - return fingerprintAllowed || faceAllowed && mKeyguardBypassController.canBypass(); + return fingerprintAllowed || (isCurrentUserUnlockedWithFace() + && mKeyguardBypassController.canBypass()); } public boolean getUserTrustIsManaged(int userId) { @@ -1684,10 +1456,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent; // however the strong auth tracker does not include the temporary lockout // mFingerprintLockedOut. + if (!mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)) { + return false; + } + boolean isFaceLockedOut = + getFaceAuthInteractor() != null && getFaceAuthInteractor().isLockedOut(); + boolean isFaceAuthStrong = + getFaceAuthInteractor() != null && getFaceAuthInteractor().isFaceAuthStrong(); + boolean isFingerprintLockedOut = isFingerprintLockedOut(); + boolean isAnyStrongBiometricLockedOut = + (isFingerprintClass3() && isFingerprintLockedOut) || (isFaceAuthStrong + && isFaceLockedOut); // Class 3 biometric lockout will lockout ALL biometrics - return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric) - && (!isFingerprintClass3() || !isFingerprintLockedOut()) - && (!isFaceClass3() || !mFaceLockedOutPermanent); + if (isAnyStrongBiometricLockedOut) { + return false; + } + return !isFaceLockedOut || !isFingerprintLockedOut; } /** @@ -1707,7 +1491,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab case FINGERPRINT: return isUnlockingWithBiometricAllowed(isFingerprintClass3()); case FACE: - return isUnlockingWithBiometricAllowed(isFaceClass3()); + return getFaceAuthInteractor() != null + && isUnlockingWithBiometricAllowed( + getFaceAuthInteractor().isFaceAuthStrong()); default: return false; } @@ -1757,14 +1543,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } if (userId == mSelectedUserInteractor.getSelectedUserId()) { - FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED.setExtraInfo( - mStrongAuthTracker.getStrongAuthForUser( - mSelectedUserInteractor.getSelectedUserId())); - // Strong auth is only reset when primary auth is used to enter the device, // so we only check whether to stop biometric listening states here - updateBiometricListeningState( - BIOMETRIC_ACTION_STOP, FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED); + updateFingerprintListeningState(BIOMETRIC_ACTION_STOP); } } @@ -1787,14 +1568,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } if (userId == mSelectedUserInteractor.getSelectedUserId()) { - FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED.setExtraInfo( - mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout( - mSelectedUserInteractor.getSelectedUserId()) ? -1 : 1); - // This is only reset when primary auth is used to enter the device, so we only check // whether to stop biometric listening states here - updateBiometricListeningState(BIOMETRIC_ACTION_STOP, - FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED); + updateFingerprintListeningState(BIOMETRIC_ACTION_STOP); } } @@ -1813,11 +1589,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab void setAssistantVisible(boolean assistantVisible) { mAssistantVisible = assistantVisible; mLogger.logAssistantVisible(mAssistantVisible); - if (isFaceAuthInteractorEnabled()) { - mFaceAuthInteractor.onAssistantTriggeredOnLockScreen(); + if (getFaceAuthInteractor() != null) { + getFaceAuthInteractor().onAssistantTriggeredOnLockScreen(); } - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); if (mAssistantVisible) { requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT, @@ -1929,14 +1704,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }; - private final FaceManager.LockoutResetCallback mFaceLockoutResetCallback - = new FaceManager.LockoutResetCallback() { - @Override - public void onLockoutReset(int sensorId) { - handleFaceLockoutReset(BIOMETRIC_LOCKOUT_NONE); - } - }; - /** * Propagates a pointer down event to keyguard. */ @@ -1998,7 +1765,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onUdfpsPointerDown(int sensorId) { mLogger.logUdfpsPointerDown(sensorId); - requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN); } /** @@ -2024,56 +1790,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }; - private final FaceManager.FaceDetectionCallback mFaceDetectionCallback - = (sensorId, userId, isStrongBiometric) -> { - // Trigger the face detected path so the bouncer can be shown - handleBiometricDetected(userId, FACE, isStrongBiometric); - }; - - @VisibleForTesting - final FaceManager.AuthenticationCallback mFaceAuthenticationCallback - = new FaceManager.AuthenticationCallback() { - - @Override - public void onAuthenticationFailed() { - handleFaceAuthFailed(); - } - - @Override - public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) { - handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric()); - } - - @Override - public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { - handleFaceHelp(helpMsgId, helpString.toString()); - } - - @Override - public void onAuthenticationError(int errMsgId, CharSequence errString) { - handleFaceError(errMsgId, errString.toString()); - } - - @Override - public void onAuthenticationAcquired(int acquireInfo) { - handleFaceAcquired(acquireInfo); - } - }; - @VisibleForTesting final DevicePostureController.Callback mPostureCallback = new DevicePostureController.Callback() { @Override public void onPostureChanged(@DevicePostureInt int posture) { - boolean currentPostureAllowsFaceAuth = doesPostureAllowFaceAuth(mPostureState); - boolean newPostureAllowsFaceAuth = doesPostureAllowFaceAuth(posture); - mPostureState = posture; - if (currentPostureAllowsFaceAuth && !newPostureAllowsFaceAuth) { - mLogger.d("New posture does not allow face auth, stopping it"); - updateFaceListeningState(BIOMETRIC_ACTION_STOP, - FACE_AUTH_UPDATED_POSTURE_CHANGED); - } - if (mPostureState == DEVICE_POSTURE_OPENED) { + if (posture == DEVICE_POSTURE_OPENED) { mLogger.d("Posture changed to open - attempting to request active unlock"); requestActiveUnlockFromWakeReason(PowerManager.WAKE_REASON_UNFOLD_DEVICE, false); @@ -2083,14 +1805,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting CancellationSignal mFingerprintCancelSignal; - @VisibleForTesting - CancellationSignal mFaceCancelSignal; private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties = Collections.emptyList(); - private List<FaceSensorPropertiesInternal> mFaceSensorProperties = Collections.emptyList(); private boolean mFingerprintLockedOut; private boolean mFingerprintLockedOutPermanent; - private boolean mFaceLockedOutPermanent; /** * When we receive a {@link android.content.Intent#ACTION_SIM_STATE_CHANGED} broadcast, @@ -2229,15 +1947,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp"); Assert.isMainThread(); - mAllowedDisplayStateWhileAwakeForFaceAuth = true; updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); - if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) { - FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason); - updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_STARTED_WAKING_UP); - } else { - mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason); - } requestActiveUnlockFromWakeReason(pmWakeReason, true); for (int i = 0; i < mCallbacks.size(); i++) { @@ -2264,7 +1974,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // which results in face auth running once on AoD. mAssistantVisible = false; mLogger.d("Started going to sleep, mGoingToSleep=true, mAssistantVisible=false"); - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_GOING_TO_SLEEP); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } protected void handleFinishedGoingToSleep(int arg1) { @@ -2276,15 +1986,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onFinishedGoingToSleep(arg1); } } - updateFaceListeningState(BIOMETRIC_ACTION_STOP, - FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP); updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } private void handleScreenTurnedOff() { Assert.isMainThread(); mHardwareFingerprintUnavailableRetryCount = 0; - mHardwareFaceUnavailableRetryCount = 0; } private void handleDreamingStateChanged(int dreamStart) { @@ -2297,9 +2004,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); - if (mIsDreaming) { - updateFaceListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_DREAM_STARTED); - } } private void handleUserUnlocked(int userId) { @@ -2344,7 +2048,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting void resetBiometricListeningState() { mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; - mFaceRunningState = BIOMETRIC_STATE_STOPPED; } @VisibleForTesting @@ -2376,17 +2079,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab SensorPrivacyManager sensorPrivacyManager, TelephonyManager telephonyManager, PackageManager packageManager, - @Nullable FaceManager faceManager, @Nullable FingerprintManager fingerprintManager, @Nullable BiometricManager biometricManager, FaceWakeUpTriggersConfig faceWakeUpTriggersConfig, DevicePostureController devicePostureController, Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider, TaskStackChangeListeners taskStackChangeListeners, - IActivityTaskManager activityTaskManagerService, - DisplayTracker displayTracker, - WakefulnessLifecycle wakefulness, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, + IActivityTaskManager activityTaskManagerService) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2413,16 +2113,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mDreamManager = dreamManager; mTelephonyManager = telephonyManager; mDevicePolicyManager = devicePolicyManager; - mPostureController = devicePostureController; mPackageManager = packageManager; mFpm = fingerprintManager; - mFaceManager = faceManager; mActiveUnlockConfig.setKeyguardUpdateMonitor(this); - mFaceAcquiredInfoIgnoreList = Arrays.stream( - mContext.getResources().getIntArray( - R.array.config_face_acquire_device_entry_ignorelist)) - .boxed() - .collect(Collectors.toSet()); mConfigFaceAuthSupportedPosture = mContext.getResources().getInteger( R.integer.config_face_auth_supported_posture); mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig; @@ -2432,9 +2125,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab .collect(Collectors.toSet()); mTaskStackChangeListeners = taskStackChangeListeners; mActivityTaskManager = activityTaskManagerService; - mWakefulness = wakefulness; - mDisplayTracker = displayTracker; - mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor); mSelectedUserInteractor = selectedUserInteractor; mHandler = new Handler(mainLooper) { @@ -2521,8 +2211,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab setAssistantVisible((boolean) msg.obj); break; case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE: - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_FP_AUTHENTICATED); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); break; case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED: updateLogoutEnabled(); @@ -2617,18 +2306,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab }); mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback); } - if (mFaceManager != null) { - mFaceManager.addAuthenticatorsRegisteredCallback( - new IFaceAuthenticatorsRegisteredCallback.Stub() { - @Override - public void onAllAuthenticatorsRegistered( - List<FaceSensorPropertiesInternal> sensors) throws RemoteException { - mFaceSensorProperties = sensors; - mLogger.d("FaceManager onAllAuthenticatorsRegistered"); - } - }); - mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback); - } if (biometricManager != null) { biometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback); @@ -2639,16 +2316,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAllAuthenticatorsRegistered( @BiometricAuthenticator.Modality int modality) { - mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED)); + mainExecutor.execute( + () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE)); } @Override public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) { mHandler.obtainMessage(MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED, modality, 0) .sendToTarget(); - mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED)); + mainExecutor.execute( + () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE)); } @Override @@ -2665,9 +2342,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }); if (mConfigFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) { - mPostureController.addCallback(mPostureCallback); + devicePostureController.addCallback(mPostureCallback); } - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ON_KEYGUARD_INIT); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener); mIsSystemUser = mUserManager.isSystemUser(); @@ -2721,10 +2398,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - private boolean isFaceSupported() { - return mFaceManager != null && !mFaceSensorProperties.isEmpty(); - } - private boolean isFingerprintSupported() { return mFpm != null && !mFingerprintSensorProperties.isEmpty(); } @@ -2760,17 +2433,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** - * @return true if there's at least one face enrolled for the given user. - */ - public boolean isFaceEnrolled(int userId) { - return mAuthController.isFaceAuthEnrolled(userId); - } - - /** * @return true if there's at least one face enrolled + * @deprecated Use {@link KeyguardFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()} */ - public boolean isFaceEnrolled() { - return isFaceEnrolled(mSelectedUserInteractor.getSelectedUserId()); + @Deprecated + public boolean isFaceEnabledAndEnrolled() { + return getFaceAuthInteractor() != null + && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled(); } private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @@ -2798,12 +2467,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED); } - private void updateBiometricListeningState(int action, - @NonNull FaceAuthUiEvent faceAuthUiEvent) { - updateFingerprintListeningState(action); - updateFaceListeningState(action, faceAuthUiEvent); - } - private void updateFingerprintListeningState(int action) { // If this message exists, we should not authenticate again until this message is // consumed by the handler @@ -2859,57 +2522,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return; } mAuthInterruptActive = active; - updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD); requestActiveUnlock(ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE, "onReach"); } - /** - * Requests face authentication if we're on a state where it's allowed. - * This will re-trigger auth in case it fails. - * @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being - * invoked. - * @return current face auth detection state, true if it is running. - * @deprecated This is being migrated to use modern architecture. - */ - @Deprecated - public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) { - mLogger.logFaceAuthRequested(reason); - updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason)); - return isFaceDetectionRunning(); - } - - /** - * In case face auth is running, cancel it. - */ - public void cancelFaceAuth() { - stopListeningForFace(FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER); - } - - private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) { - if (isFaceAuthInteractorEnabled()) return; - // If this message exists, we should not authenticate again until this message is - // consumed by the handler - if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) { - return; - } - mHandler.removeCallbacks(mRetryFaceAuthentication); - boolean shouldListenForFace = shouldListenForFace(); - if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) { - if (action == BIOMETRIC_ACTION_START) { - mLogger.v("Ignoring stopListeningForFace()"); - return; - } - stopListeningForFace(faceAuthUiEvent); - } else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING && shouldListenForFace) { - if (action == BIOMETRIC_ACTION_STOP) { - mLogger.v("Ignoring startListeningForFace()"); - return; - } - startListeningForFace(faceAuthUiEvent); - } - } - @Nullable private InstanceId getKeyguardSessionId() { return mSessionTrackerProvider.get().getSessionId(SESSION_KEYGUARD); @@ -2994,8 +2609,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @NonNull ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin, String extraReason ) { - final boolean canFaceBypass = isFaceEnrolled() && mKeyguardBypassController != null - && mKeyguardBypassController.canBypass(); + final boolean canFaceBypass = + isFaceEnabledAndEnrolled() && mKeyguardBypassController != null + && mKeyguardBypassController.canBypass(); requestActiveUnlock( requestOrigin, extraReason, canFaceBypass @@ -3022,8 +2638,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void setAlternateBouncerShowing(boolean showing) { mAlternateBouncerShowing = showing; if (mAlternateBouncerShowing) { - updateFaceListeningState(BIOMETRIC_ACTION_START, - FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN); requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT, "alternateBouncer"); @@ -3101,17 +2715,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mSelectedUserInteractor.getSelectedUserId(), false); } - private boolean shouldListenForFaceAssistant() { - BiometricAuthenticated face = mUserFaceAuthenticated.get( - mSelectedUserInteractor.getSelectedUserId()); - return mAssistantVisible - // There can be intermediate states where mKeyguardShowing is false but - // mKeyguardOccluded is true, we don't want to run face auth in such a scenario. - && (mKeyguardShowing && mKeyguardOccluded) - && !(face != null && face.mAuthenticated) - && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false); - } - private boolean shouldTriggerActiveUnlockForAssistant() { return mAssistantVisible && mKeyguardOccluded && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false); @@ -3195,107 +2798,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * If face auth is allows to scan on this exact moment. + * + * @deprecated Use {@link KeyguardFaceAuthInteractor#canFaceAuthRun()} */ + @Deprecated public boolean shouldListenForFace() { - if (mFaceManager == null) { - // Device does not have face auth - return false; - } - - if (isFaceAuthInteractorEnabled()) { - return mFaceAuthInteractor.canFaceAuthRun(); - } - - final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED; - final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive - && !statusBarShadeLocked; - final int user = mSelectedUserInteractor.getSelectedUserId(); - final boolean faceAuthAllowed = isUnlockingWithBiometricAllowed(FACE); - final boolean canBypass = mKeyguardBypassController != null - && mKeyguardBypassController.canBypass(); - // There's no reason to ask the HAL for authentication when the user can dismiss the - // bouncer because the user is trusted, unless we're bypassing and need to auto-dismiss - // the lock screen even when TrustAgents are keeping the device unlocked. - final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass; - - // If the device supports face detection (without authentication), if bypass is enabled, - // allow face detection to happen even if stronger auth is required. When face is detected, - // we show the bouncer. However, if the user manually locked down the device themselves, - // never attempt to detect face. - final boolean supportsDetect = isFaceSupported() - && mFaceSensorProperties.get(0).supportsFaceDetection - && canBypass && !mPrimaryBouncerIsOrWillBeShowing - && !isUserInLockdown(user); - final boolean faceAuthAllowedOrDetectionIsNeeded = faceAuthAllowed || supportsDetect; - - // If the face or fp has recently been authenticated do not attempt to authenticate again. - final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user); - final boolean faceDisabledForUser = isFaceDisabled(user); - final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user); - final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant(); - final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown(); - final boolean isPostureAllowedForFaceAuth = doesPostureAllowFaceAuth(mPostureState); - // Only listen if this KeyguardUpdateMonitor belongs to the system user. There is an - // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. - final boolean shouldListen = - (mPrimaryBouncerFullyShown - || mAuthInterruptActive - || mOccludingAppRequestingFace - || awakeKeyguard - || shouldListenForFaceAssistant - || isUdfpsFingerDown - || mAlternateBouncerShowing) - && !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded - && !mKeyguardGoingAway && biometricEnabledForUser - && faceAuthAllowedOrDetectionIsNeeded && mIsSystemUser - && (!mSecureCameraLaunched || mAlternateBouncerShowing) - && faceAndFpNotAuthenticated - && !mGoingToSleep - && isPostureAllowedForFaceAuth - && mAllowedDisplayStateWhileAwakeForFaceAuth; - - // Aggregate relevant fields for debug logging. - logListenerModelData( - new KeyguardFaceListenModel( - System.currentTimeMillis(), - user, - shouldListen, - mAllowedDisplayStateWhileAwakeForFaceAuth, - mAlternateBouncerShowing, - mAuthInterruptActive, - biometricEnabledForUser, - mPrimaryBouncerFullyShown, - faceAndFpNotAuthenticated, - faceAuthAllowed, - faceDisabledForUser, - isFaceLockedOut(), - mGoingToSleep, - awakeKeyguard, - mKeyguardGoingAway, - shouldListenForFaceAssistant, - mOccludingAppRequestingFace, - isPostureAllowedForFaceAuth, - mSecureCameraLaunched, - supportsDetect, - mSwitchingUser, - mIsSystemUser, - isUdfpsFingerDown, - userNotTrustedOrDetectionIsNeeded)); - - return shouldListen; - } - - private boolean doesPostureAllowFaceAuth(@DevicePostureInt int posture) { - return mConfigFaceAuthSupportedPosture == DEVICE_POSTURE_UNKNOWN - || (posture == mConfigFaceAuthSupportedPosture); + return getFaceAuthInteractor() != null && getFaceAuthInteractor().canFaceAuthRun(); } - /** - * If the current device posture allows face auth to run. - */ - public boolean doesCurrentPostureAllowFaceAuth() { - return doesPostureAllowFaceAuth(mPostureState); - } private void logListenerModelData(@NonNull KeyguardListenModel model) { mLogger.logKeyguardListenerModel(model); @@ -3303,8 +2813,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintListenBuffer.insert((KeyguardFingerprintListenModel) model); } else if (model instanceof KeyguardActiveUnlockModel) { mActiveUnlockTriggerBuffer.insert((KeyguardActiveUnlockModel) model); - } else if (model instanceof KeyguardFaceListenModel) { - mFaceListenBuffer.insert((KeyguardFaceListenModel) model); } } @@ -3355,85 +2863,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - private void startListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) { - final int userId = mSelectedUserInteractor.getSelectedUserId(); - final boolean unlockPossible = isUnlockWithFacePossible(userId); - if (mFaceCancelSignal != null) { - mLogger.logUnexpectedFaceCancellationSignalState(mFaceRunningState, unlockPossible); - } - - if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) { - setFaceRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING); - return; - } else if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) { - // Waiting for ERROR_CANCELED before requesting auth again - return; - } - mLogger.logStartedListeningForFace(mFaceRunningState, faceAuthUiEvent); - mUiEventLogger.logWithInstanceIdAndPosition( - faceAuthUiEvent, - 0, - null, - getKeyguardSessionId(), - faceAuthUiEvent.getExtraInfo() - ); - mLogger.logFaceUnlockPossible(unlockPossible); - if (unlockPossible) { - mFaceCancelSignal = new CancellationSignal(); - - final FaceAuthenticateOptions faceAuthenticateOptions = - new SysUiFaceAuthenticateOptions( - userId, - faceAuthUiEvent, - faceAuthUiEvent == FACE_AUTH_UPDATED_STARTED_WAKING_UP - ? faceAuthUiEvent.getExtraInfo() - : WAKE_REASON_UNKNOWN - ).toFaceAuthenticateOptions(); - // This would need to be updated for multi-sensor devices - final boolean supportsFaceDetection = isFaceSupported() - && mFaceSensorProperties.get(0).supportsFaceDetection; - if (!isUnlockingWithBiometricAllowed(FACE)) { - final boolean udfpsFingerprintAuthRunning = isUdfpsSupported() - && isFingerprintDetectionRunning(); - if (supportsFaceDetection && !udfpsFingerprintAuthRunning) { - // Run face detection. (If a face is detected, show the bouncer.) - mLogger.v("startListeningForFace - detect"); - mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, - faceAuthenticateOptions); - } else { - // Don't run face detection. Instead, inform the user - // face auth is unavailable and how to proceed. - // (ie: "Use fingerprint instead" or "Swipe up to open") - mLogger.v("Ignoring \"startListeningForFace - detect\". " - + "Informing user face isn't available."); - mFaceAuthenticationCallback.onAuthenticationHelp( - BIOMETRIC_HELP_FACE_NOT_AVAILABLE, - mContext.getResources().getString( - R.string.keyguard_face_unlock_unavailable) - ); - return; - } - } else { - mLogger.v("startListeningForFace - authenticate"); - final boolean isBypassEnabled = mKeyguardBypassController != null - && mKeyguardBypassController.isBypassEnabled(); - mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal, - mFaceAuthenticationCallback, null /* handler */, - faceAuthenticateOptions); - } - setFaceRunningState(BIOMETRIC_STATE_RUNNING); - } - } - public boolean isFingerprintLockedOut() { return mFingerprintLockedOut || mFingerprintLockedOutPermanent; } + /** + * @deprecated Use {@link KeyguardFaceAuthInteractor#isLockedOut()} + */ + @Deprecated public boolean isFaceLockedOut() { - if (isFaceAuthInteractorEnabled()) { - return getFaceAuthInteractor().isLockedOut(); - } - return mFaceLockedOutPermanent; + return getFaceAuthInteractor() != null && getFaceAuthInteractor().isLockedOut(); } /** @@ -3444,7 +2883,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @return {@code true} if possible. */ public boolean isUnlockingWithBiometricsPossible(int userId) { - return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId); + return isUnlockWithFacePossible() || isUnlockWithFingerprintPossible(userId); } /** @@ -3455,8 +2894,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @return {@code true} if possible. */ public boolean isUnlockingWithNonStrongBiometricsPossible(int userId) { - return (!isFaceClass3() && isUnlockWithFacePossible(userId)) - || (isFingerprintClass3() && isUnlockWithFingerprintPossible(userId)); + if (getFaceAuthInteractor() != null && !getFaceAuthInteractor().isFaceAuthStrong()) { + if (isUnlockWithFacePossible()) { + return true; + } + } + return isFingerprintClass3() && isUnlockWithFingerprintPossible(userId); } @SuppressLint("MissingPermission") @@ -3466,16 +2909,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** - * @deprecated This is being migrated to use modern architecture. + * @deprecated Use {@link KeyguardFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()} */ @VisibleForTesting @Deprecated - public boolean isUnlockWithFacePossible(int userId) { - if (isFaceAuthInteractorEnabled()) { - return getFaceAuthInteractor() != null + public boolean isUnlockWithFacePossible() { + return getFaceAuthInteractor() != null && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled(); - } - return isFaceSupported() && isFaceEnrolled(userId) && !isFaceDisabled(userId); } private void notifyAboutEnrollmentChange(@BiometricAuthenticator.Modality int modality) { @@ -3513,25 +2953,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - private void stopListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) { - if (isFaceAuthInteractorEnabled()) return; - mLogger.v("stopListeningForFace()"); - mLogger.logStoppedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason()); - mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId()); - if (mFaceRunningState == BIOMETRIC_STATE_RUNNING) { - if (mFaceCancelSignal != null) { - mFaceCancelSignal.cancel(); - mFaceCancelSignal = null; - mHandler.removeCallbacks(mFaceCancelNotReceived); - mHandler.postDelayed(mFaceCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT); - } - setFaceRunningState(BIOMETRIC_STATE_CANCELLING); - } - if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) { - setFaceRunningState(BIOMETRIC_STATE_CANCELLING); - } - } - private boolean isDeviceProvisionedInSettingsDb() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; @@ -3617,13 +3038,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - // Immediately stop previous biometric listening states. - // Resetting lockout states updates the biometric listening states. - if (isFaceSupported()) { - stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING); - handleFaceLockoutReset(mFaceManager.getLockoutModeForUser( - mFaceSensorProperties.get(0).sensorId, userId)); - } if (isFingerprintSupported()) { stopListeningForFingerprint(); handleFingerprintLockoutReset(mFpm.getLockoutModeForUser( @@ -3866,8 +3280,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting protected void handleKeyguardReset() { mLogger.d("handleKeyguardReset"); - updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_KEYGUARD_RESET); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition(); } @@ -3935,8 +3348,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onKeyguardBouncerFullyShowingChanged(mPrimaryBouncerFullyShown); } } - updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN); } } @@ -4071,8 +3482,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } mSwitchingUser = switching; // Since this comes in on a binder thread, we need to post it first - mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_UPDATED_USER_SWITCHING)); + mHandler.post(() -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE)); } private void sendUpdates(KeyguardUpdateMonitorCallback callback) { @@ -4161,7 +3571,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void clearBiometricRecognized(int unlockedUser) { Assert.isMainThread(); mUserFingerprintAuthenticated.clear(); - mUserFaceAuthenticated.clear(); mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser); mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser); mLogger.d("clearBiometricRecognized"); @@ -4394,12 +3803,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return isFingerprintSupported() && isClass3Biometric(mFingerprintSensorProperties.get(0)); } - @VisibleForTesting - protected boolean isFaceClass3() { - // This assumes that there is at most one face sensor property - return isFaceSupported() && isClass3Biometric(mFaceSensorProperties.get(0)); - } - private boolean isClass3Biometric(SensorPropertiesInternal sensorProperties) { return sensorProperties.sensorStrength == SensorProperties.STRENGTH_STRONG; } @@ -4411,8 +3814,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mStatusBarStateController.removeCallback(mStatusBarStateControllerListener); mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener); - if (isFaceAuthInteractorEnabled()) { - mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener); + if (getFaceAuthInteractor() != null) { + getFaceAuthInteractor().unregisterListener(mFaceAuthenticationListener); } if (mDeviceProvisionedObserver != null) { @@ -4432,7 +3835,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker); mTrustManager.unregisterTrustListener(this); - mDisplayTracker.removeCallback(mDisplayCallback); mHandler.removeCallbacksAndMessages(null); } @@ -4446,7 +3848,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mSelectedUserInteractor.getSelectedUserId())); pw.println(" getUserUnlockedWithBiometric()=" + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId())); - pw.println(" isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled()); pw.println(" SIM States:"); for (SimData data : mSimDatas.values()) { pw.println(" " + data.toString()); @@ -4519,50 +3920,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintListenBuffer.toList() ).printTableData(pw); } - if (isFaceSupported()) { - final int userId = mSelectedUserInteractor.getSelectedUserId(true); - final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); - BiometricAuthenticated face = mUserFaceAuthenticated.get(userId); - pw.println(" Face authentication state (user=" + userId + ")"); - pw.println(" isFaceClass3=" + isFaceClass3()); - pw.println(" allowed=" - + (face != null && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric))); - pw.println(" auth'd=" - + (face != null && face.mAuthenticated)); - pw.println(" authSinceBoot=" - + getStrongAuthTracker().hasUserAuthenticatedSinceBoot()); - pw.println(" disabled(DPM)=" + isFaceDisabled(userId)); - pw.println(" possible=" + isUnlockWithFacePossible(userId)); - pw.println(" listening: actual=" + mFaceRunningState - + " expected=(" + (shouldListenForFace() ? 1 : 0)); - pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); - pw.println(" isNonStrongBiometricAllowedAfterIdleTimeout=" - + mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(userId)); - pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); - pw.println(" mFaceLockedOutPermanent=" + mFaceLockedOutPermanent); - pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId)); - pw.println(" mSecureCameraLaunched=" + mSecureCameraLaunched); - pw.println(" mPrimaryBouncerFullyShown=" + mPrimaryBouncerFullyShown); - pw.println(" mNeedsSlowUnlockTransition=" + mNeedsSlowUnlockTransition); - new DumpsysTableLogger( - "KeyguardFaceListen", - KeyguardFaceListenModel.TABLE_HEADERS, - mFaceListenBuffer.toList() - ).printTableData(pw); - } else if (mFaceManager != null && mFaceSensorProperties.isEmpty()) { - final int userId = mSelectedUserInteractor.getSelectedUserId(true); - pw.println(" Face state (user=" + userId + ")"); - pw.println(" mFaceSensorProperties.isEmpty=" - + mFaceSensorProperties.isEmpty()); - pw.println(" mFaceManager.isHardwareDetected=" - + mFaceManager.isHardwareDetected()); - - new DumpsysTableLogger( - "KeyguardFaceListen", - KeyguardFingerprintListenModel.TABLE_HEADERS, - mFingerprintListenBuffer.toList() - ).printTableData(pw); - } + final int userId = mSelectedUserInteractor.getSelectedUserId(true); + final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); + pw.println(" authSinceBoot=" + + getStrongAuthTracker().hasUserAuthenticatedSinceBoot()); + pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println("ActiveUnlockRunning=" + mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId())); new DumpsysTableLogger( @@ -4577,13 +3939,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * Cancels all operations in the scheduler if it is hung for 10 seconds. */ public void startBiometricWatchdog() { - final boolean isFaceAuthInteractorEnabled = isFaceAuthInteractorEnabled(); mBackgroundExecutor.execute(() -> { Trace.beginSection("#startBiometricWatchdog"); - if (mFaceManager != null && !isFaceAuthInteractorEnabled) { - mLogger.scheduleWatchdog("face"); - mFaceManager.scheduleWatchdog(); - } if (mFpm != null) { mLogger.scheduleWatchdog("fingerprint"); mFpm.scheduleWatchdog(); diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 5bf8d635f8ee..055ca565a933 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -20,14 +20,12 @@ import android.content.Intent import android.hardware.biometrics.BiometricConstants.LockoutMode import android.hardware.biometrics.BiometricSourceType import android.os.PowerManager -import android.os.PowerManager.WakeReason import android.telephony.ServiceState import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.TelephonyManager import com.android.keyguard.ActiveUnlockConfig -import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.KeyguardListenModel import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.TrustGrantFlags @@ -102,14 +100,6 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { logBuffer.log(TAG, ERROR, {}, { logMsg }, exception = ex) } - fun logFaceAuthDisabledForUser(userId: Int) { - logBuffer.log( - TAG, - DEBUG, - { int1 = userId }, - { "Face authentication disabled by DPM for userId: $int1" } - ) - } fun logFaceAuthError(msgId: Int, originalErrMsg: String) { logBuffer.log( TAG, @@ -131,31 +121,10 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { ) } - fun logFaceAuthRequested(reason: String?) { - logBuffer.log(TAG, DEBUG, { str1 = reason }, { "requestFaceAuth() reason=$str1" }) - } - fun logFaceAuthSuccess(userId: Int) { logBuffer.log(TAG, DEBUG, { int1 = userId }, { "Face auth succeeded for user $int1" }) } - fun logFaceLockoutReset(@LockoutMode mode: Int) { - logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFaceLockoutReset: $int1" }) - } - - fun logFaceRunningState(faceRunningState: Int) { - logBuffer.log(TAG, DEBUG, { int1 = faceRunningState }, { "faceRunningState: $int1" }) - } - - fun logFaceUnlockPossible(isFaceUnlockPossible: Boolean) { - logBuffer.log( - TAG, - DEBUG, - { bool1 = isFaceUnlockPossible }, - { "isUnlockWithFacePossible: $bool1" } - ) - } - fun logFingerprintAuthForWrongUser(authUserId: Int) { logBuffer.log( FP_LOG_TAG, @@ -301,15 +270,6 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { logBuffer.log(TAG, VERBOSE, { str1 = "$callback" }, { "*** register callback for $str1" }) } - fun logRetryingAfterFaceHwUnavailable(retryCount: Int) { - logBuffer.log( - TAG, - WARNING, - { int1 = retryCount }, - { "Retrying face after HW unavailable, attempt $int1" } - ) - } - fun logRetryAfterFpErrorWithDelay(msgId: Int, errString: String?, delay: Int) { logBuffer.log( TAG, @@ -419,43 +379,6 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" }) } - fun logStartedListeningForFace(faceRunningState: Int, faceAuthUiEvent: FaceAuthUiEvent) { - logBuffer.log( - TAG, - VERBOSE, - { - int1 = faceRunningState - str1 = faceAuthUiEvent.reason - str2 = faceAuthUiEvent.extraInfoToString() - }, - { "startListeningForFace(): $int1, reason: $str1 $str2" } - ) - } - - fun logStartedListeningForFaceFromWakeUp(faceRunningState: Int, @WakeReason pmWakeReason: Int) { - logBuffer.log( - TAG, - VERBOSE, - { - int1 = faceRunningState - str1 = PowerManager.wakeReasonToString(pmWakeReason) - }, - { "startListeningForFace(): $int1, reason: wakeUp-$str1" } - ) - } - - fun logStoppedListeningForFace(faceRunningState: Int, faceAuthReason: String) { - logBuffer.log( - TAG, - VERBOSE, - { - int1 = faceRunningState - str1 = faceAuthReason - }, - { "stopListeningForFace(): currentFaceRunningState: $int1, reason: $str1" } - ) - } - fun logSubInfo(subInfo: SubscriptionInfo?) { logBuffer.log(TAG, DEBUG, { str1 = "$subInfo" }, { "SubInfo:$str1" }) } @@ -476,22 +399,6 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { logBuffer.log(TAG, DEBUG, { int1 = sensorId }, { "onUdfpsPointerUp, sensorId: $int1" }) } - fun logUnexpectedFaceCancellationSignalState(faceRunningState: Int, unlockPossible: Boolean) { - logBuffer.log( - TAG, - ERROR, - { - int1 = faceRunningState - bool1 = unlockPossible - }, - { - "Cancellation signal is not null, high chance of bug in " + - "face auth lifecycle management. " + - "Face state: $int1, unlockPossible: $bool1" - } - ) - } - fun logUnexpectedFpCancellationSignalState( fingerprintRunningState: Int, unlockPossible: Boolean @@ -588,15 +495,6 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { ) } - fun logSkipUpdateFaceListeningOnWakeup(@WakeReason pmWakeReason: Int) { - logBuffer.log( - TAG, - VERBOSE, - { str1 = PowerManager.wakeReasonToString(pmWakeReason) }, - { "Skip updating face listening state on wakeup from $str1" } - ) - } - fun logTaskStackChangedForAssistant(assistantVisible: Boolean) { logBuffer.log( TAG, @@ -648,18 +546,6 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { ) } - fun logFaceEnrolledUpdated(oldValue: Boolean, newValue: Boolean) { - logBuffer.log( - TAG, - DEBUG, - { - bool1 = oldValue - bool2 = newValue - }, - { "Face enrolled state changed: old: $bool1, new: $bool2" } - ) - } - fun logTrustUsuallyManagedUpdated( userId: Int, oldValue: Boolean, @@ -745,18 +631,6 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { ) } - fun logFingerprintHelp(helpMsgId: Int, helpString: CharSequence) { - logBuffer.log( - FP_LOG_TAG, - DEBUG, - { - int1 = helpMsgId - str1 = "$helpString" - }, - { "fingerprint help message: $int1, $str1" } - ) - } - fun logFingerprintAcquired(acquireInfo: Int) { logBuffer.log( FP_LOG_TAG, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 57e252dd9929..8fe42b536b1e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -100,7 +100,6 @@ import javax.inject.Inject; import javax.inject.Provider; import kotlin.Unit; - import kotlinx.coroutines.CoroutineScope; /** @@ -1099,6 +1098,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, // TODO(b/141025588): Create separate methods for handling hard and soft errors. final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT + || error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL || isCameraPrivacyEnabled); if (mCurrentDialog != null) { if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt index bbdcadbb19c6..cb750493ba26 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt @@ -20,12 +20,10 @@ import android.content.res.Resources import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityNodeInfo -import com.android.keyguard.FaceAuthApiRequestReason -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor +import com.android.systemui.res.R import javax.inject.Inject /** @@ -37,12 +35,11 @@ class FaceAuthAccessibilityDelegate @Inject constructor( @Main private val resources: Resources, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val faceAuthInteractor: KeyguardFaceAuthInteractor, ) : View.AccessibilityDelegate() { override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) { super.onInitializeAccessibilityNodeInfo(host, info) - if (keyguardUpdateMonitor.shouldListenForFace()) { + if (faceAuthInteractor.canFaceAuthRun()) { val clickActionToRetryFace = AccessibilityNodeInfo.AccessibilityAction( AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id, @@ -54,7 +51,6 @@ constructor( override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean { return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) { - keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION) faceAuthInteractor.onAccessibilityAction() true } else super.performAccessibilityAction(host, action, args) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index b064391f74b5..85a119c389c1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -52,6 +52,7 @@ import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -63,7 +64,6 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.util.LatencyTracker; -import com.android.keyguard.FaceAuthApiRequestReason; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityLaunchAnimator; @@ -78,9 +78,9 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -150,7 +150,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; @NonNull private final VibratorHelper mVibrator; - @NonNull private final FeatureFlags mFeatureFlags; @NonNull private final FalsingManager mFalsingManager; @NonNull private final PowerManager mPowerManager; @NonNull private final AccessibilityManager mAccessibilityManager; @@ -281,7 +280,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { fromUdfpsView ), mActivityLaunchAnimator, - mFeatureFlags, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsKeyguardAccessibilityDelegate, @@ -318,10 +316,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { return; } mAcquiredReceived = true; - final UdfpsView view = mOverlay.getOverlayView(); - if (view != null && isOptical()) { - unconfigureDisplay(view); - } + final View view = mOverlay.getTouchOverlay(); + unconfigureDisplay(view); tryAodSendFingerUp(); }); } @@ -339,7 +335,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (mOverlay == null || mOverlay.isHiding()) { return; } - mOverlay.getOverlayView().setDebugMessage(message); + if (!DeviceEntryUdfpsRefactor.isEnabled()) { + ((UdfpsView) mOverlay.getTouchOverlay()).setDebugMessage(message); + } }); } @@ -506,6 +504,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f && !mAlternateBouncerInteractor.isVisibleState()) || mPrimaryBouncerInteractor.isInTransit()) { + Log.w(TAG, "ignoring touch due to qsDragProcess or primaryBouncerInteractor"); return false; } if (event.getAction() == MotionEvent.ACTION_DOWN @@ -563,7 +562,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } mAttemptedToDismissKeyguard = false; onFingerUp(requestId, - mOverlay.getOverlayView(), + mOverlay.getTouchOverlay(), data.getPointerId(), data.getX(), data.getY(), @@ -597,7 +596,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (shouldPilfer && !mPointerPilfered && getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) { mInputManager.pilferPointers( - mOverlay.getOverlayView().getViewRootImpl().getInputToken()); + mOverlay.getTouchOverlay().getViewRootImpl().getInputToken()); mPointerPilfered = true; } @@ -605,9 +604,15 @@ public class UdfpsController implements DozeReceiver, Dumpable { } private boolean shouldTryToDismissKeyguard() { - return mOverlay != null - && mOverlay.getAnimationViewController() - instanceof UdfpsKeyguardViewControllerAdapter + boolean onKeyguard = false; + if (DeviceEntryUdfpsRefactor.isEnabled()) { + onKeyguard = mKeyguardStateController.isShowing(); + } else { + onKeyguard = mOverlay != null + && mOverlay.getAnimationViewController() + instanceof UdfpsKeyguardViewControllerAdapter; + } + return onKeyguard && mKeyguardStateController.canDismissLockScreen() && !mAttemptedToDismissKeyguard; } @@ -623,7 +628,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull DumpManager dumpManager, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, - @NonNull FeatureFlags featureFlags, @NonNull FalsingManager falsingManager, @NonNull PowerManager powerManager, @NonNull AccessibilityManager accessibilityManager, @@ -670,7 +674,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mDumpManager = dumpManager; mDialogManager = dialogManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mFeatureFlags = featureFlags; mFalsingManager = falsingManager; mPowerManager = powerManager; mAccessibilityManager = accessibilityManager; @@ -737,9 +740,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { @VisibleForTesting public void playStartHaptic() { if (mAccessibilityManager.isTouchExplorationEnabled()) { - if (mOverlay != null && mOverlay.getOverlayView() != null) { + if (mOverlay != null && mOverlay.getTouchOverlay() != null) { mVibrator.performHapticFeedback( - mOverlay.getOverlayView(), + mOverlay.getTouchOverlay(), HapticFeedbackConstants.CONTEXT_CLICK ); } else { @@ -751,10 +754,11 @@ public class UdfpsController implements DozeReceiver, Dumpable { @Override public void dozeTimeTick() { - if (mOverlay != null) { - final UdfpsView view = mOverlay.getOverlayView(); + if (mOverlay != null && mOverlay.getTouchOverlay() instanceof UdfpsView) { + DeviceEntryUdfpsRefactor.assertInLegacyMode(); + final View view = mOverlay.getTouchOverlay(); if (view != null) { - view.dozeTimeTick(); + ((UdfpsView) view).dozeTimeTick(); } } } @@ -797,7 +801,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (mOverlay != null) { // Reset the controller back to its starting state. - final UdfpsView oldView = mOverlay.getOverlayView(); + final View oldView = mOverlay.getTouchOverlay(); if (oldView != null) { onFingerUp(mOverlay.getRequestId(), oldView); } @@ -813,9 +817,21 @@ public class UdfpsController implements DozeReceiver, Dumpable { } - private void unconfigureDisplay(@NonNull UdfpsView view) { - if (view.isDisplayConfigured()) { - view.unconfigureDisplay(); + private void unconfigureDisplay(View view) { + if (!isOptical()) { + return; + } + if (DeviceEntryUdfpsRefactor.isEnabled()) { + if (mUdfpsDisplayMode != null) { + mUdfpsDisplayMode.disable(null); // beverlt + } + } else { + if (view != null) { + UdfpsView udfpsView = (UdfpsView) view; + if (udfpsView.isDisplayConfigured()) { + udfpsView.unconfigureDisplay(); + } + } } } @@ -837,10 +853,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { } mKeyguardViewManager.showPrimaryBouncer(true); - // play the same haptic as the LockIconViewController longpress - if (mOverlay != null && mOverlay.getOverlayView() != null) { + // play the same haptic as the DeviceEntryIcon longpress + if (mOverlay != null && mOverlay.getTouchOverlay() != null) { mVibrator.performHapticFeedback( - mOverlay.getOverlayView(), + mOverlay.getTouchOverlay(), UdfpsController.LONG_PRESS ); } else { @@ -909,8 +925,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { return; } cancelAodSendFingerUpAction(); - if (mOverlay != null && mOverlay.getOverlayView() != null) { - onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView()); + if (mOverlay != null && mOverlay.getTouchOverlay() != null) { + onFingerUp(mOverlay.getRequestId(), mOverlay.getTouchOverlay()); } } @@ -996,20 +1012,22 @@ public class UdfpsController implements DozeReceiver, Dumpable { playStartHaptic(); mKeyguardFaceAuthInteractor.onUdfpsSensorTouched(); - if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) { - mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN); - } } mOnFingerDown = true; mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y, minor, major, orientation, time, gestureStart, isAod); Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); - final UdfpsView view = mOverlay.getOverlayView(); + + final View view = mOverlay.getTouchOverlay(); if (view != null && isOptical()) { if (mIgnoreRefreshRate) { dispatchOnUiReady(requestId); } else { - view.configureDisplay(() -> dispatchOnUiReady(requestId)); + if (DeviceEntryUdfpsRefactor.isEnabled()) { + mUdfpsDisplayMode.enable(() -> dispatchOnUiReady(requestId)); + } else { + ((UdfpsView) view).configureDisplay(() -> dispatchOnUiReady(requestId)); + } } } @@ -1018,7 +1036,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } - private void onFingerUp(long requestId, @NonNull UdfpsView view) { + private void onFingerUp(long requestId, @NonNull View view) { onFingerUp( requestId, view, @@ -1035,7 +1053,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { private void onFingerUp( long requestId, - @NonNull UdfpsView view, + View view, int pointerId, float x, float y, @@ -1056,9 +1074,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } mOnFingerDown = false; - if (isOptical()) { - unconfigureDisplay(view); - } + unconfigureDisplay(view); cancelAodSendFingerUpAction(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index a5bd89a15e5a..2d54f7ac8e7d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -46,11 +46,11 @@ import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams +import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -96,7 +96,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val controllerCallback: IUdfpsOverlayControllerCallback, private val onTouch: (View, MotionEvent, Boolean) -> Boolean, private val activityLaunchAnimator: ActivityLaunchAnimator, - private val featureFlags: FeatureFlags, private val primaryBouncerInteractor: PrimaryBouncerInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, @@ -104,9 +103,22 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val transitionInteractor: KeyguardTransitionInteractor, private val selectedUserInteractor: SelectedUserInteractor, ) { - /** The view, when [isShowing], or null. */ - var overlayView: UdfpsView? = null + private var overlayViewLegacy: UdfpsView? = null private set + private var overlayTouchView: UdfpsTouchOverlay? = null + + /** + * Get the current UDFPS overlay touch view which is a different View depending on whether + * the DeviceEntryUdfpsRefactor flag is enabled or not. + * @return The view, when [isShowing], else null + */ + fun getTouchOverlay(): View? { + return if (DeviceEntryUdfpsRefactor.isEnabled) { + overlayTouchView + } else { + overlayViewLegacy + } + } private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams() private var sensorBounds: Rect = Rect() @@ -132,15 +144,15 @@ class UdfpsControllerOverlay @JvmOverloads constructor( /** If the overlay is currently showing. */ val isShowing: Boolean - get() = overlayView != null + get() = getTouchOverlay() != null /** Opposite of [isShowing]. */ val isHiding: Boolean - get() = overlayView == null + get() = getTouchOverlay() == null /** The animation controller if the overlay [isShowing]. */ val animationViewController: UdfpsAnimationViewController<*>? - get() = overlayView?.animationViewController + get() = overlayViewLegacy?.animationViewController private var touchExplorationEnabled = false @@ -158,28 +170,48 @@ class UdfpsControllerOverlay @JvmOverloads constructor( /** Show the overlay or return false and do nothing if it is already showing. */ @SuppressLint("ClickableViewAccessibility") fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean { - if (overlayView == null) { + if (getTouchOverlay() == null) { overlayParams = params sensorBounds = Rect(params.sensorBounds) try { - overlayView = (inflater.inflate( - R.layout.udfps_view, null, false - ) as UdfpsView).apply { - overlayParams = params - setUdfpsDisplayModeProvider(udfpsDisplayModeProvider) - val animation = inflateUdfpsAnimation(this, controller) - if (animation != null) { - animation.init() - animationViewController = animation - } - // This view overlaps the sensor area - // prevent it from being selectable during a11y - if (requestReason.isImportantForAccessibility()) { - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + if (DeviceEntryUdfpsRefactor.isEnabled) { + overlayTouchView = (inflater.inflate( + R.layout.udfps_touch_overlay, null, false + ) as UdfpsTouchOverlay).apply { + // This view overlaps the sensor area + // prevent it from being selectable during a11y + if (requestReason.isImportantForAccessibility()) { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + } + windowManager.addView(this, coreLayoutParams.updateDimensions(null)) } + // TODO (b/305234447): Bind view model to UdfpsTouchOverlay here to control + // the visibility (sometimes, even if UDFPS is running, the UDFPS UI can be + // obscured and we don't want to accept touches. ie: for enrollment don't accept + // touches when the shade is expanded and for keyguard: don't accept touches + // depending on the keyguard & shade state + } else { + overlayViewLegacy = (inflater.inflate( + R.layout.udfps_view, null, false + ) as UdfpsView).apply { + overlayParams = params + setUdfpsDisplayModeProvider(udfpsDisplayModeProvider) + val animation = inflateUdfpsAnimation(this, controller) + if (animation != null) { + animation.init() + animationViewController = animation + } + // This view overlaps the sensor area + // prevent it from being selectable during a11y + if (requestReason.isImportantForAccessibility()) { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + } - windowManager.addView(this, coreLayoutParams.updateDimensions(animation)) - sensorRect = sensorBounds + windowManager.addView(this, coreLayoutParams.updateDimensions(animation)) + sensorRect = sensorBounds + } + } + getTouchOverlay()?.apply { touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled overlayTouchListener = TouchExplorationStateChangeListener { if (accessibilityManager.isTouchExplorationEnabled) { @@ -193,7 +225,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } } accessibilityManager.addTouchExplorationStateChangeListener( - overlayTouchListener!! + overlayTouchListener!! ) overlayTouchListener?.onTouchExplorationStateChanged(true) } @@ -211,6 +243,8 @@ class UdfpsControllerOverlay @JvmOverloads constructor( view: UdfpsView, controller: UdfpsController ): UdfpsAnimationViewController<*>? { + DeviceEntryUdfpsRefactor.assertInLegacyMode() + val isEnrollment = when (requestReason) { REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true else -> false @@ -237,39 +271,27 @@ class UdfpsControllerOverlay @JvmOverloads constructor( ) } REASON_AUTH_KEYGUARD -> { - if (DeviceEntryUdfpsRefactor.isEnabled) { - // note: empty controller, currently shows no visual affordance - // instead SysUI will show the fingerprint icon in its DeviceEntryIconView - UdfpsBpViewController( - view.addUdfpsView(R.layout.udfps_bp_view), - statusBarStateController, - primaryBouncerInteractor, - dialogManager, - dumpManager - ) - } else { - UdfpsKeyguardViewControllerLegacy( - view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { - updateSensorLocation(sensorBounds) - }, - statusBarStateController, - statusBarKeyguardViewManager, - keyguardUpdateMonitor, - dumpManager, - transitionController, - configurationController, - keyguardStateController, - unlockedScreenOffAnimationController, - dialogManager, - controller, - activityLaunchAnimator, - primaryBouncerInteractor, - alternateBouncerInteractor, - udfpsKeyguardAccessibilityDelegate, - selectedUserInteractor, - transitionInteractor, - ) - } + UdfpsKeyguardViewControllerLegacy( + view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { + updateSensorLocation(sensorBounds) + }, + statusBarStateController, + statusBarKeyguardViewManager, + keyguardUpdateMonitor, + dumpManager, + transitionController, + configurationController, + keyguardStateController, + unlockedScreenOffAnimationController, + dialogManager, + controller, + activityLaunchAnimator, + primaryBouncerInteractor, + alternateBouncerInteractor, + udfpsKeyguardAccessibilityDelegate, + selectedUserInteractor, + transitionInteractor, + ) } REASON_AUTH_BP -> { // note: empty controller, currently shows no visual affordance @@ -302,19 +324,26 @@ class UdfpsControllerOverlay @JvmOverloads constructor( fun hide(): Boolean { val wasShowing = isShowing - overlayView?.apply { + overlayViewLegacy?.apply { if (isDisplayConfigured) { unconfigureDisplay() } + animationViewController = null + } + if (DeviceEntryUdfpsRefactor.isEnabled) { + udfpsDisplayModeProvider.disable(null) + } + getTouchOverlay()?.apply { windowManager.removeView(this) setOnTouchListener(null) setOnHoverListener(null) - animationViewController = null overlayTouchListener?.let { accessibilityManager.removeTouchExplorationStateChangeListener(it) } } - overlayView = null + + overlayViewLegacy = null + overlayTouchView = null overlayTouchListener = null return wasShowing @@ -392,7 +421,14 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean { - if (animation !is UdfpsKeyguardViewControllerAdapter) { + val keyguardNotShowing = + if (DeviceEntryUdfpsRefactor.isEnabled) { + !keyguardStateController.isShowing + } else { + animation !is UdfpsKeyguardViewControllerAdapter + } + + if (keyguardNotShowing) { // always rotate view if we're not on the keyguard return true } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 35c3ded9e984..6954eb66cfa6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -50,7 +50,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch /** Class that coordinates non-HBM animations during keyguard authentication. */ @@ -215,12 +214,12 @@ open class UdfpsKeyguardViewControllerLegacy( suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job { return scope.launch { transitionInteractor.dozeAmountTransition.collect { transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, - ) - } + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, + ) + } } } @@ -286,7 +285,6 @@ open class UdfpsKeyguardViewControllerLegacy( keyguardStateController.removeCallback(keyguardStateControllerCallback) statusBarStateController.removeCallback(stateListener) keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI) - keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false) configurationController.removeCallback(configurationListener) if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) { lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null @@ -334,14 +332,9 @@ open class UdfpsKeyguardViewControllerLegacy( if (udfpsAffordanceWasNotShowing) { view.animateInUdfpsBouncer(null) } - if (keyguardStateController.isOccluded) { - keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true) - } view.announceForAccessibility( view.context.getString(R.string.accessibility_fingerprint_bouncer) ) - } else { - keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false) } updateAlpha() updatePauseAuth() diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt new file mode 100644 index 000000000000..2484c339a1d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.biometrics.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout + +/** + * A translucent (not visible to the user) view that receives touches to send to FingerprintManager + * for fingerprint authentication. + */ +class UdfpsTouchOverlay(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index 56dfa5ed337c..aa7758f9380f 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -39,6 +39,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.shared.system.SysUiStatsLog @@ -75,6 +76,7 @@ constructor( private val trustRepository: TrustRepository, @Application private val applicationScope: CoroutineScope, private val selectedUserInteractor: SelectedUserInteractor, + private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor, ) { private val passiveAuthBouncerDelay = context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong() @@ -414,15 +416,12 @@ constructor( /** Whether we want to wait to show the bouncer in case passive auth succeeds. */ private fun usePrimaryBouncerPassiveAuthDelay(): Boolean { - val canRunFaceAuth = - keyguardStateController.isFaceEnrolled && - keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) && - keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth() val canRunActiveUnlock = currentUserActiveUnlockRunning && keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState() - return !needsFullscreenBouncer() && (canRunFaceAuth || canRunActiveUnlock) + return !needsFullscreenBouncer() && + (keyguardFaceAuthInteractor.canFaceAuthRun() || canRunActiveUnlock) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt new file mode 100644 index 000000000000..5385442092b9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt @@ -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.systemui.bouncer.ui.helper + +import androidx.annotation.VisibleForTesting + +/** Enumerates all known adaptive layout configurations. */ +enum class BouncerSceneLayout { + /** The default UI with the bouncer laid out normally. */ + STANDARD, + /** The bouncer is displayed vertically stacked with the user switcher. */ + STACKED, + /** The bouncer is displayed side-by-side with the user switcher or an empty space. */ + SIDE_BY_SIDE, + /** The bouncer is split in two with both sides shown side-by-side. */ + SPLIT, +} + +/** Enumerates the supported window size classes. */ +enum class SizeClass { + COMPACT, + MEDIUM, + EXPANDED, +} + +/** + * Internal version of `calculateLayout` in the System UI Compose library, extracted here to allow + * for testing that's not dependent on Compose. + */ +@VisibleForTesting +fun calculateLayoutInternal( + width: SizeClass, + height: SizeClass, + isSideBySideSupported: Boolean, +): BouncerSceneLayout { + return when (height) { + SizeClass.COMPACT -> BouncerSceneLayout.SPLIT + SizeClass.MEDIUM -> + when (width) { + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD + SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD + SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + } + SizeClass.EXPANDED -> + when (width) { + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD + SizeClass.MEDIUM -> BouncerSceneLayout.STACKED + SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + } + }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported } + ?: BouncerSceneLayout.STANDARD +} diff --git a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt index 5e6caf0d0317..27c9b3fa7c9e 100644 --- a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt +++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt @@ -11,20 +11,17 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.systemui.common -import com.android.systemui.common.domain.interactor.ConfigurationInteractor -import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl +package com.android.systemui.common.data + import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl import dagger.Binds import dagger.Module @Module -abstract class CommonModule { +abstract class CommonDataLayerModule { @Binds abstract fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository - - @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt new file mode 100644 index 000000000000..7be2eaf7b105 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.common.domain + +import com.android.systemui.common.domain.interactor.ConfigurationInteractor +import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl +import dagger.Binds +import dagger.Module + +@Module +abstract class CommonDomainLayerModule { + @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor +} diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt new file mode 100644 index 000000000000..8d04e3d338a0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.common.ui.view + +import android.view.View + +/** + * Set this view's [View#importantForAccessibility] to [View#IMPORTANT_FOR_ACCESSIBILITY_YES] or + * [View#IMPORTANT_FOR_ACCESSIBILITY_NO] based on [value]. + */ +fun View.setImportantForAccessibilityYesNo(value: Boolean) { + importantForAccessibility = + if (value) View.IMPORTANT_FOR_ACCESSIBILITY_YES else View.IMPORTANT_FOR_ACCESSIBILITY_NO +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index fd7f641c2e61..e630fd4e3c54 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -27,6 +27,7 @@ import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.smartspace.data.repository.SmartspaceRepository import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -46,6 +47,7 @@ constructor( private val widgetRepository: CommunalWidgetRepository, mediaRepository: CommunalMediaRepository, smartspaceRepository: SmartspaceRepository, + keyguardInteractor: KeyguardInteractor, private val appWidgetHost: AppWidgetHost, private val editWidgetsActivityStarter: EditWidgetsActivityStarter ) { @@ -67,6 +69,8 @@ constructor( val isCommunalShowing: Flow<Boolean> = communalRepository.desiredScene.map { it == CommunalSceneKey.Communal } + val isKeyguardVisible: Flow<Boolean> = keyguardInteractor.isKeyguardVisible + /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */ fun onSceneChanged(newScene: CommunalSceneKey) { communalRepository.setDesiredScene(newScene) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index b4ab5fbfcc1a..4d8e893cb747 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -28,6 +28,8 @@ abstract class BaseCommunalViewModel( private val communalInteractor: CommunalInteractor, val mediaHost: MediaHost, ) { + val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible + val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene fun onSceneChanged(scene: CommunalSceneKey) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index eaf2d5741e23..11bde6bd7af0 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -39,7 +39,6 @@ constructor( tutorialInteractor: CommunalTutorialInteractor, @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, ) : BaseCommunalViewModel(communalInteractor, mediaHost) { - @OptIn(ExperimentalCoroutinesApi::class) override val communalContent: Flow<List<CommunalContentModel>> = tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode -> diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 482832b03a72..0405ca43320f 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -42,7 +42,8 @@ import com.android.systemui.bouncer.domain.interactor.BouncerInteractorModule; import com.android.systemui.bouncer.ui.BouncerViewModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; -import com.android.systemui.common.CommonModule; +import com.android.systemui.common.data.CommonDataLayerModule; +import com.android.systemui.common.domain.CommonDomainLayerModule; import com.android.systemui.communal.dagger.CommunalModule; import com.android.systemui.complication.dagger.ComplicationComponent; import com.android.systemui.controls.dagger.ControlsModule; @@ -176,7 +177,8 @@ import javax.inject.Named; ClipboardOverlayModule.class, ClockRegistryModule.class, CommunalModule.class, - CommonModule.class, + CommonDataLayerModule.class, + CommonDomainLayerModule.class, ConnectivityModule.class, ControlsModule.class, CoroutinesModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt index 31a5d37a4450..4bfc9484f733 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -97,7 +97,7 @@ class FaceScanningProviderFactory @Inject constructor( } fun canShowFaceScanningAnim(): Boolean { - return hasProviders && keyguardUpdateMonitor.isFaceEnrolled + return hasProviders && keyguardUpdateMonitor.isFaceEnabledAndEnrolled } fun shouldShowFaceScanningAnim(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 83c16ae9ea78..6a0e88246027 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -19,6 +19,7 @@ package com.android.systemui.flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import javax.inject.Inject /** A class in which engineers can define flag dependencies */ @@ -26,6 +27,7 @@ import javax.inject.Inject class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, handler: Handler) : FlagDependenciesBase(featureFlags, handler) { override fun defineDependencies() { + NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 7d541070f146..b357b563f791 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -152,10 +152,6 @@ object Flags { @JvmField val REFACTOR_GETCURRENTUSER = unreleasedFlag("refactor_getcurrentuser", teamfood = true) - /** Flag to control the migration of face auth to modern architecture. */ - // TODO(b/262838215): Tracking bug - @JvmField val FACE_AUTH_REFACTOR = releasedFlag("face_auth_refactor") - /** Flag to control the revamp of keyguard biometrics progress animation */ // TODO(b/244313043): Tracking bug @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp") @@ -464,12 +460,6 @@ object Flags { val WALLPAPER_MULTI_CROP = sysPropBooleanFlag("persist.wm.debug.wallpaper_multi_crop", default = false) - // TODO(b/290220798): Tracking Bug - @Keep - @JvmField - val ENABLE_PIP2_IMPLEMENTATION = - sysPropBooleanFlag("persist.wm.debug.enable_pip2_implementation", default = false) - // 1200 - predictive back @Keep @JvmField @@ -713,7 +703,7 @@ object Flags { /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */ @JvmField - val BLUETOOTH_QS_TILE_DIALOG = unreleasedFlag("bluetooth_qs_tile_dialog") + val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog") // TODO(b/300995746): Tracking Bug /** A resource flag for whether the communal service is enabled. */ diff --git a/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt b/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt new file mode 100644 index 000000000000..bc1cc4f4bc56 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt @@ -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 com.android.systemui.fold.ui.helper + +import androidx.annotation.VisibleForTesting +import androidx.window.layout.FoldingFeature +import androidx.window.layout.WindowLayoutInfo + +sealed interface FoldPosture { + /** A foldable device that's fully closed/folded or a device that doesn't support folding. */ + data object Folded : FoldPosture + /** A foldable that's halfway open with the hinge held vertically. */ + data object Book : FoldPosture + /** A foldable that's halfway open with the hinge held horizontally. */ + data object Tabletop : FoldPosture + /** A foldable that's fully unfolded / flat. */ + data object FullyUnfolded : FoldPosture +} + +/** + * Internal version of `foldPosture` in the System UI Compose library, extracted here to allow for + * testing that's not dependent on Compose. + */ +@VisibleForTesting +fun foldPostureInternal(layoutInfo: WindowLayoutInfo?): FoldPosture { + return layoutInfo + ?.displayFeatures + ?.firstNotNullOfOrNull { it as? FoldingFeature } + .let { foldingFeature -> + when (foldingFeature?.state) { + null -> FoldPosture.Folded + FoldingFeature.State.HALF_OPENED -> foldingFeature.orientation.toHalfwayPosture() + FoldingFeature.State.FLAT -> + if (foldingFeature.isSeparating) { + // Dual screen device. + foldingFeature.orientation.toHalfwayPosture() + } else { + FoldPosture.FullyUnfolded + } + else -> error("Unsupported state \"${foldingFeature.state}\"") + } + } +} + +private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture { + return when (this) { + FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop + FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book + else -> error("Unsupported orientation \"$this\"") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index fe9865b2d1dd..53ec3dead6c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -3401,7 +3401,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } if (mPowerGestureIntercepted && mOccluded && isSecure() - && mUpdateMonitor.isFaceEnrolled()) { + && mUpdateMonitor.isFaceEnabledAndEnrolled()) { flags |= StatusBarManager.DISABLE_RECENT; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index c4dfe9afeb2a..8ef26621362f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -69,7 +69,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull @@ -89,7 +88,7 @@ import kotlinx.coroutines.withContext */ interface DeviceEntryFaceAuthRepository { /** Provide the current face authentication state for device entry. */ - val isAuthenticated: Flow<Boolean> + val isAuthenticated: StateFlow<Boolean> /** Whether face auth can run at this point. */ val canRunFaceAuth: StateFlow<Boolean> @@ -199,8 +198,7 @@ constructor( private val canRunDetection: StateFlow<Boolean> private val _isAuthenticated = MutableStateFlow(false) - override val isAuthenticated: Flow<Boolean> - get() = _isAuthenticated + override val isAuthenticated: StateFlow<Boolean> = _isAuthenticated private var cancellationInProgress = MutableStateFlow(false) @@ -243,61 +241,52 @@ constructor( .collect(Collectors.toSet()) dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this) - if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) { - canRunFaceAuth = - listOf( - *gatingConditionsForAuthAndDetect(), - Pair(isLockedOut.isFalse(), "isNotInLockOutState"), - Pair( - trustRepository.isCurrentUserTrusted.isFalse(), - "currentUserIsNotTrusted" - ), - Pair( - biometricSettingsRepository.isFaceAuthCurrentlyAllowed, - "isFaceAuthCurrentlyAllowed" - ), - Pair(isAuthenticated.isFalse(), "faceNotAuthenticated"), - ) - .andAllFlows("canFaceAuthRun", faceAuthLog) - .flowOn(mainDispatcher) - .stateIn(applicationScope, SharingStarted.Eagerly, false) - - // Face detection can run only when lockscreen bypass is enabled - // & detection is supported - // & biometric unlock is not allowed - // or user is trusted by trust manager & we want to run face detect to dismiss - // keyguard - canRunDetection = - listOf( - *gatingConditionsForAuthAndDetect(), - Pair(isBypassEnabled, "isBypassEnabled"), - Pair( - biometricSettingsRepository.isFaceAuthCurrentlyAllowed - .isFalse() - .or(trustRepository.isCurrentUserTrusted), - "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted" - ), - // We don't want to run face detect if fingerprint can be used to unlock the - // device - // but it's not possible to authenticate with FP from the bouncer (UDFPS) - Pair( - and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning) - .isFalse(), - "udfpsAuthIsNotPossibleAnymore" - ) + canRunFaceAuth = + listOf( + *gatingConditionsForAuthAndDetect(), + Pair(isLockedOut.isFalse(), "isNotInLockOutState"), + Pair(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserIsNotTrusted"), + Pair( + biometricSettingsRepository.isFaceAuthCurrentlyAllowed, + "isFaceAuthCurrentlyAllowed" + ), + Pair(isAuthenticated.isFalse(), "faceNotAuthenticated"), + ) + .andAllFlows("canFaceAuthRun", faceAuthLog) + .flowOn(mainDispatcher) + .stateIn(applicationScope, SharingStarted.Eagerly, false) + + // Face detection can run only when lockscreen bypass is enabled + // & detection is supported + // & biometric unlock is not allowed + // or user is trusted by trust manager & we want to run face detect to dismiss + // keyguard + canRunDetection = + listOf( + *gatingConditionsForAuthAndDetect(), + Pair(isBypassEnabled, "isBypassEnabled"), + Pair( + biometricSettingsRepository.isFaceAuthCurrentlyAllowed + .isFalse() + .or(trustRepository.isCurrentUserTrusted), + "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted" + ), + // We don't want to run face detect if fingerprint can be used to unlock the + // device + // but it's not possible to authenticate with FP from the bouncer (UDFPS) + Pair( + and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(), + "udfpsAuthIsNotPossibleAnymore" ) - .andAllFlows("canFaceDetectRun", faceDetectLog) - .flowOn(mainDispatcher) - .stateIn(applicationScope, SharingStarted.Eagerly, false) - observeFaceAuthGatingChecks() - observeFaceDetectGatingChecks() - observeFaceAuthResettingConditions() - listenForSchedulingWatchdog() - processPendingAuthRequests() - } else { - canRunFaceAuth = MutableStateFlow(false).asStateFlow() - canRunDetection = MutableStateFlow(false).asStateFlow() - } + ) + .andAllFlows("canFaceDetectRun", faceDetectLog) + .flowOn(mainDispatcher) + .stateIn(applicationScope, SharingStarted.Eagerly, false) + observeFaceAuthGatingChecks() + observeFaceDetectGatingChecks() + observeFaceAuthResettingConditions() + listenForSchedulingWatchdog() + processPendingAuthRequests() } private fun listenForSchedulingWatchdog() { @@ -454,8 +443,8 @@ constructor( if (errorStatus.isLockoutError()) { _isLockedOut.value = true } - _authenticationStatus.value = errorStatus _isAuthenticated.value = false + _authenticationStatus.value = errorStatus if (errorStatus.isHardwareError()) { faceAuthLogger.hardwareError(errorStatus) handleFaceHardwareError() @@ -477,8 +466,17 @@ constructor( } override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) { - _authenticationStatus.value = SuccessFaceAuthenticationStatus(result) + // Update _isAuthenticated before _authenticationStatus is updated. There are + // consumers that receive the face authentication updates through a long chain of + // callbacks + // _authenticationStatus -> KeyguardUpdateMonitor -> KeyguardStateController -> + // onUnlockChanged + // These consumers then query the isAuthenticated boolean. This makes sure that the + // boolean is updated to new value before the event is propagated. + // TODO (b/310592822): once all consumers can use the new system directly, we don't + // have to worry about this ordering. _isAuthenticated.value = true + _authenticationStatus.value = SuccessFaceAuthenticationStatus(result) faceAuthLogger.faceAuthSuccess(result) onFaceAuthRequestCompleted() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt index c8cb9e6aa0dd..f4a74f03696c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt @@ -24,6 +24,7 @@ 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.emptyFlow /** @@ -34,11 +35,9 @@ import kotlinx.coroutines.flow.emptyFlow */ @SysUISingleton class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository { - override val isAuthenticated: Flow<Boolean> - get() = emptyFlow() + override val isAuthenticated: StateFlow<Boolean> = MutableStateFlow(false) - private val _canRunFaceAuth = MutableStateFlow(false) - override val canRunFaceAuth: StateFlow<Boolean> = _canRunFaceAuth + override val canRunFaceAuth: StateFlow<Boolean> = MutableStateFlow(false) override val authenticationStatus: Flow<FaceAuthenticationStatus> get() = emptyFlow() @@ -46,11 +45,9 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA override val detectionStatus: Flow<FaceDetectionStatus> get() = emptyFlow() - private val _isLockedOut = MutableStateFlow(false) - override val isLockedOut: StateFlow<Boolean> = _isLockedOut + override val isLockedOut: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() - private val _isAuthRunning = MutableStateFlow(false) - override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning + override val isAuthRunning: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() override val isBypassEnabled: Flow<Boolean> get() = emptyFlow() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt index 85b0f4fb864b..5ed70b526f1b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt @@ -44,6 +44,8 @@ interface KeyguardFaceAuthInteractor { /** Whether face auth is enrolled and enabled for the current user */ fun isFaceAuthEnabledAndEnrolled(): Boolean + /** Whether the current user is authenticated successfully with face auth */ + fun isAuthenticated(): Boolean /** * Register listener for use from code that cannot use [authenticationStatus] or * [detectionStatus] @@ -53,9 +55,6 @@ interface KeyguardFaceAuthInteractor { /** Unregister previously registered listener */ fun unregisterListener(listener: FaceAuthenticationListener) - /** Whether the face auth interactor is enabled or not. */ - fun isEnabled(): Boolean - fun onUdfpsSensorTouched() fun onAssistantTriggeredOnLockScreen() fun onDeviceLifted() @@ -65,6 +64,9 @@ interface KeyguardFaceAuthInteractor { fun onPrimaryBouncerUserInput() fun onAccessibilityAction() fun onWalletLaunched() + + /** Whether face auth is considered class 3 */ + fun isFaceAuthStrong(): Boolean } /** @@ -81,4 +83,10 @@ interface FaceAuthenticationListener { /** Receive status updates whenever face detection runs */ fun onDetectionStatusChanged(status: FaceDetectionStatus) + + fun onLockoutStateChanged(isLockedOut: Boolean) + + fun onRunningStateChanged(isRunning: Boolean) + + fun onAuthEnrollmentStateChanged(enrolled: Boolean) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 949c940bdebc..91b671599eb0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -31,8 +31,6 @@ import com.android.systemui.common.shared.model.Position import com.android.systemui.common.shared.model.SharedNotificationContainerPosition import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel @@ -76,8 +74,7 @@ class KeyguardInteractor constructor( private val repository: KeyguardRepository, private val commandQueue: CommandQueue, - private val powerInteractor: PowerInteractor, - featureFlags: FeatureFlags, + powerInteractor: PowerInteractor, sceneContainerFlags: SceneContainerFlags, bouncerRepository: KeyguardBouncerRepository, configurationRepository: ConfigurationRepository, @@ -197,22 +194,18 @@ constructor( /** Whether camera is launched over keyguard. */ val isSecureCameraActive: Flow<Boolean> by lazy { - if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) { - combine( - isKeyguardVisible, - primaryBouncerShowing, - onCameraLaunchDetected, - ) { isKeyguardVisible, isPrimaryBouncerShowing, cameraLaunchEvent -> - when { - isKeyguardVisible -> false - isPrimaryBouncerShowing -> false - else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP - } + combine( + isKeyguardVisible, + primaryBouncerShowing, + onCameraLaunchDetected, + ) { isKeyguardVisible, isPrimaryBouncerShowing, cameraLaunchEvent -> + when { + isKeyguardVisible -> false + isPrimaryBouncerShowing -> false + else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP } - .onStart { emit(false) } - } else { - flowOf(false) - } + } + .onStart { emit(false) } } /** The approximate location on the screen of the fingerprint sensor, if one is available. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt index fbadde63a6b9..cd6ab31f3908 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt @@ -42,9 +42,12 @@ class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInt override fun isLockedOut(): Boolean = false - override fun isEnabled() = false override fun isFaceAuthEnabledAndEnrolled(): Boolean = false + override fun isFaceAuthStrong(): Boolean = false + + override fun isAuthenticated(): Boolean = false + override fun registerListener(listener: FaceAuthenticationListener) {} override fun unregisterListener(listener: FaceAuthenticationListener) {} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 2641846251cc..ae356cd94350 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -30,8 +30,6 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository @@ -45,6 +43,7 @@ import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -71,10 +70,9 @@ constructor( @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, private val repository: DeviceEntryFaceAuthRepository, - private val primaryBouncerInteractor: PrimaryBouncerInteractor, + private val primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, - private val featureFlags: FeatureFlags, private val faceAuthenticationLogger: FaceAuthenticationLogger, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, @@ -88,16 +86,16 @@ constructor( private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf() override fun start() { - if (!isEnabled()) { - return - } - // This is required because fingerprint state required for the face auth repository is - // backed by KeyguardUpdateMonitor. KeyguardUpdateMonitor constructor accesses the biometric - // state which makes lazy injection not an option. + // Todo(b/310594096): there is a dependency cycle introduced by the repository depending on + // KeyguardBypassController, which in turn depends on KeyguardUpdateMonitor through + // its other dependencies. Once bypassEnabled state is available through a repository, we + // can break that cycle and inject this interactor directly into KeyguardUpdateMonitor keyguardUpdateMonitor.setFaceAuthInteractor(this) observeFaceAuthStateUpdates() faceAuthenticationLogger.interactorStarted() - primaryBouncerInteractor.isShowing + primaryBouncerInteractor + .get() + .isShowing .whenItFlipsToTrue() .onEach { faceAuthenticationLogger.bouncerVisibilityChanged() @@ -176,7 +174,7 @@ constructor( FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING, // Fallback to detection if bouncer is not showing so that we can detect a // face and then show the bouncer to the user if face auth can't run - fallbackToDetect = !primaryBouncerInteractor.isBouncerShowing() + fallbackToDetect = !primaryBouncerInteractor.get().isBouncerShowing() ) } } @@ -231,9 +229,8 @@ constructor( override fun canFaceAuthRun(): Boolean = repository.canRunFaceAuth.value - override fun isEnabled(): Boolean { - return featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR) - } + override fun isFaceAuthStrong(): Boolean = + facePropertyRepository.sensorInfo.value?.strength == SensorStrength.STRONG override fun onPrimaryBouncerUserInput() { repository.cancel() @@ -248,29 +245,24 @@ constructor( override val detectionStatus = repository.detectionStatus private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) { - if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) { - if (repository.isLockedOut.value) { - faceAuthenticationStatusOverride.value = - ErrorFaceAuthenticationStatus( - BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT, - context.resources.getString(R.string.keyguard_face_unlock_unavailable) - ) - } else { - faceAuthenticationStatusOverride.value = null - faceAuthenticationLogger.authRequested(uiEvent) - repository.requestAuthenticate(uiEvent, fallbackToDetection = fallbackToDetect) - } + if (repository.isLockedOut.value) { + faceAuthenticationStatusOverride.value = + ErrorFaceAuthenticationStatus( + BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT, + context.resources.getString(R.string.keyguard_face_unlock_unavailable) + ) } else { - faceAuthenticationLogger.ignoredFaceAuthTrigger( - uiEvent, - ignoredReason = "Skipping face auth request because feature flag is false" - ) + faceAuthenticationStatusOverride.value = null + faceAuthenticationLogger.authRequested(uiEvent) + repository.requestAuthenticate(uiEvent, fallbackToDetection = fallbackToDetect) } } override fun isFaceAuthEnabledAndEnrolled(): Boolean = biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.value + override fun isAuthenticated(): Boolean = repository.isAuthenticated.value + private fun observeFaceAuthStateUpdates() { authenticationStatus .onEach { authStatusUpdate -> @@ -284,6 +276,21 @@ constructor( } .flowOn(mainDispatcher) .launchIn(applicationScope) + repository.isLockedOut + .onEach { lockedOut -> listeners.forEach { it.onLockoutStateChanged(lockedOut) } } + .flowOn(mainDispatcher) + .launchIn(applicationScope) + repository.isAuthRunning + .onEach { running -> listeners.forEach { it.onRunningStateChanged(running) } } + .flowOn(mainDispatcher) + .launchIn(applicationScope) + + biometricSettingsRepository.isFaceAuthEnrolledAndEnabled + .onEach { enrolledAndEnabled -> + listeners.forEach { it.onAuthEnrollmentStateChanged(enrolledAndEnabled) } + } + .flowOn(mainDispatcher) + .launchIn(applicationScope) } companion object { 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 index 99529a100b07..9a50d8370525 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @@ -49,12 +50,22 @@ constructor( transitionInteractor.startedKeyguardState.map { keyguardState -> keyguardState == KeyguardState.AOD } + + private fun getColor(usingBackgroundProtection: Boolean): Int { + return if (usingBackgroundProtection) { + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) + } else { + Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent) + } + } + private val color: Flow<Int> = - configurationRepository.onAnyConfigurationChange - .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) } - .onStart { - emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)) - } + deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBgProtection -> + configurationRepository.onAnyConfigurationChange + .map { getColor(useBgProtection) } + .onStart { emit(getColor(useBgProtection)) } + } + private val useAodIconVariant: Flow<Boolean> = combine(isShowingAod, deviceEntryUdfpsInteractor.isUdfpsSupported) { isTransitionToAod, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 315626b6fcae..b3c7d3790527 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -25,7 +25,6 @@ import com.android.systemui.Flags.newAodTransition import com.android.systemui.common.shared.model.SharedNotificationContainerPosition import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -67,7 +66,6 @@ constructor( private val context: Context, private val deviceEntryInteractor: DeviceEntryInteractor, private val dozeParameters: DozeParameters, - private val featureFlags: FeatureFlagsClassic, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index f2db088ced83..44232ffb3ad7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -19,12 +19,18 @@ package com.android.systemui.media.controls.util import android.app.StatusBarManager import android.os.UserHandle import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.scene.shared.flag.SceneContainerFlags import javax.inject.Inject @SysUISingleton -class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { +class MediaFlags +@Inject +constructor( + private val featureFlags: FeatureFlagsClassic, + private val sceneContainerFlags: SceneContainerFlags +) { /** * Check whether media control actions should be based on PlaybackState instead of notification */ @@ -48,4 +54,8 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** Check whether we allow remote media to generate resume controls */ fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME) + + /** Check whether to use flexiglass layout */ + fun isFlexiglassEnabled() = + sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt new file mode 100644 index 000000000000..898298c58b32 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.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.media.controls.util + +import com.android.systemui.Flags +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the media_in_scene_container flag state. */ +@Suppress("NOTHING_TO_INLINE") +object MediaInSceneContainerFlag { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER + + /** Is the flag enabled? */ + @JvmStatic + inline val isEnabled + get() = Flags.mediaInSceneContainer() + + /** + * 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/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 58c4f0d029c7..ef7296743f20 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -131,6 +131,9 @@ class EmojiHelper { + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" + "?)*"; + // Not all JDKs support emoji patterns, including the one errorprone runs under, which + // makes it think that this is an invalid pattern. + @SuppressWarnings("InvalidPatternSyntax") static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index fa18b35b215e..052c0daf0b56 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -12,6 +12,7 @@ import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.util.AttributeSet; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -22,6 +23,7 @@ import android.view.animation.OvershootInterpolator; import android.widget.Scroller; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; @@ -43,6 +45,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private static final int NO_PAGE = -1; private static final int REVEAL_SCROLL_DURATION_MILLIS = 750; + private static final int SINGLE_PAGE_SCROLL_DURATION_MILLIS = 300; private static final float BOUNCE_ANIMATION_TENSION = 1.3f; private static final long BOUNCE_ANIMATION_DURATION = 450L; private static final int TILE_ANIMATION_STAGGER_DELAY = 85; @@ -63,8 +66,9 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private PageListener mPageListener; private boolean mListening; - private Scroller mScroller; + @VisibleForTesting Scroller mScroller; + /* set of animations used to indicate which tiles were just revealed */ @Nullable private AnimatorSet mBounceAnimatorSet; private float mLastExpansion; @@ -306,6 +310,38 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageIndicator = indicator; mPageIndicator.setNumPages(mPages.size()); mPageIndicator.setLocation(mPageIndicatorPosition); + mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> { + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { + // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need + // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we + // have a chance to intercept ACTION_UP. + if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) { + scrollByX(getDeltaXForKeyboardScrolling(keyCode), + SINGLE_PAGE_SCROLL_DURATION_MILLIS); + } + return true; + } + return false; + }); + } + + private int getDeltaXForKeyboardScrolling(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) { + return -getWidth(); + } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT + && getCurrentItem() != mPages.size() - 1) { + return getWidth(); + } + return 0; + } + + private void scrollByX(int x, int durationMillis) { + if (x != 0) { + mScroller.startScroll(/* startX= */ getScrollX(), /* startY= */ getScrollY(), + /* dx= */ x, /* dy= */ 0, /* duration= */ durationMillis); + // scroller just sets its state, we need to invalidate view to actually start scrolling + postInvalidateOnAnimation(); + } } @Override @@ -596,9 +632,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { }); setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated. int dx = getWidth() * lastPageNumber; - mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0, - REVEAL_SCROLL_DURATION_MILLIS); - postInvalidateOnAnimation(); + scrollByX(isLayoutRtl() ? -dx : dx, REVEAL_SCROLL_DURATION_MILLIS); } private boolean shouldNotRunAnimation(Set<String> tilesToReveal) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 346d5c30a63e..e5e1e8445e94 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.qs.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -33,14 +32,10 @@ import kotlinx.coroutines.flow.map class QuickSettingsSceneViewModel @Inject constructor( - private val deviceEntryInteractor: DeviceEntryInteractor, val shadeHeaderViewModel: ShadeHeaderViewModel, val qsSceneAdapter: QSSceneAdapter, val notifications: NotificationsPlaceholderViewModel, ) { - /** Notifies that some content in quick settings was clicked. */ - fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry() - val destinationScenes = qsSceneAdapter.isCustomizing.map { customizing -> if (customizing) { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java index b30bc56aeeb0..bc5090f14d23 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java @@ -30,12 +30,11 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; +import com.android.systemui.res.R; import com.android.systemui.classifier.Classifier; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.haptics.slider.SeekableSliderEventProducer; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.res.R; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.util.ViewController; @@ -282,7 +281,6 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV private final VibratorHelper mVibratorHelper; private final SystemClock mSystemClock; private final CoroutineDispatcher mMainDispatcher; - private final ActivityStarter mActivityStarter; @Inject public Factory( @@ -290,14 +288,13 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV UiEventLogger uiEventLogger, VibratorHelper vibratorHelper, SystemClock clock, - @Main CoroutineDispatcher mainDispatcher, - ActivityStarter activityStarter) { + @Main CoroutineDispatcher mainDispatcher + ) { mFalsingManager = falsingManager; mUiEventLogger = uiEventLogger; mVibratorHelper = vibratorHelper; mSystemClock = clock; mMainDispatcher = mainDispatcher; - mActivityStarter = activityStarter; } /** @@ -313,8 +310,6 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV int layout = getLayout(); BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context) .inflate(layout, viewRoot, false); - root.setActivityStarter(mActivityStarter); - BrightnessSliderHapticPlugin plugin; if (hapticBrightnessSlider()) { plugin = new BrightnessSliderHapticPluginImpl( diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java index 5ecf07f5a264..c88549224183 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java @@ -33,7 +33,6 @@ import androidx.annotation.Nullable; import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; /** @@ -42,7 +41,6 @@ import com.android.systemui.res.R; */ public class BrightnessSliderView extends FrameLayout { - private ActivityStarter mActivityStarter; @NonNull private ToggleSeekBar mSlider; private DispatchTouchEventListener mListener; @@ -59,10 +57,6 @@ public class BrightnessSliderView extends FrameLayout { super(context, attrs); } - public void setActivityStarter(@NonNull ActivityStarter activityStarter) { - mActivityStarter = activityStarter; - } - // Inflated from quick_settings_brightness_dialog @Override protected void onFinishInflate() { @@ -71,7 +65,6 @@ public class BrightnessSliderView extends FrameLayout { mSlider = requireViewById(R.id.slider); mSlider.setAccessibilityLabel(getContentDescription().toString()); - mSlider.setActivityStarter(mActivityStarter); // Finds the progress drawable. Assumes brightness_progress_drawable.xml try { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java index 6ec10da28000..a5a0ae70045e 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java @@ -23,9 +23,8 @@ import android.view.MotionEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.SeekBar; -import androidx.annotation.NonNull; - import com.android.settingslib.RestrictedLockUtils; +import com.android.systemui.Dependency; import com.android.systemui.plugins.ActivityStarter; public class ToggleSeekBar extends SeekBar { @@ -33,8 +32,6 @@ public class ToggleSeekBar extends SeekBar { private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null; - private ActivityStarter mActivityStarter; - public ToggleSeekBar(Context context) { super(context); } @@ -52,7 +49,7 @@ public class ToggleSeekBar extends SeekBar { if (mEnforcedAdmin != null) { Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent( mContext, mEnforcedAdmin); - mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0); return true; } if (!isEnabled()) { @@ -77,8 +74,4 @@ public class ToggleSeekBar extends SeekBar { public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) { mEnforcedAdmin = admin; } - - public void setActivityStarter(@NonNull ActivityStarter activityStarter) { - mActivityStarter = activityStarter; - } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index a44b4b4f11fe..c06e9a443a7b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -98,7 +98,6 @@ import com.android.internal.policy.SystemBarUtils; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.LatencyTracker; import com.android.keyguard.ActiveUnlockConfig; -import com.android.keyguard.FaceAuthApiRequestReason; import com.android.keyguard.KeyguardClockSwitch.ClockSize; import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; @@ -184,6 +183,8 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -604,6 +605,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; + private final ActiveNotificationsInteractor mActiveNotificationsInteractor; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final KeyguardInteractor mKeyguardInteractor; private final PowerInteractor mPowerInteractor; @@ -774,6 +776,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump KeyguardInteractor keyguardInteractor, ActivityStarter activityStarter, SharedNotificationContainerInteractor sharedNotificationContainerInteractor, + ActiveNotificationsInteractor activeNotificationsInteractor, KeyguardViewConfigurator keyguardViewConfigurator, KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, SplitShadeStateController splitShadeStateController, @@ -804,6 +807,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor; + mActiveNotificationsInteractor = activeNotificationsInteractor; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; mKeyguardViewConfigurator = keyguardViewConfigurator; @@ -1795,9 +1799,14 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private boolean hasVisibleNotifications() { - return mNotificationStackScrollLayoutController - .getVisibleNotificationCount() != 0 - || mMediaDataManager.hasActiveMediaOrRecommendation(); + if (FooterViewRefactor.isEnabled()) { + return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue() + || mMediaDataManager.hasActiveMediaOrRecommendation(); + } else { + return mNotificationStackScrollLayoutController + .getVisibleNotificationCount() != 0 + || mMediaDataManager.hasActiveMediaOrRecommendation(); + } } /** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */ @@ -2956,10 +2965,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Try triggering face auth, this "might" run. Check // KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run. mKeyguardFaceAuthInteractor.onNotificationPanelClicked(); - boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth( - FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); - if (didFaceAuthRun) { + if (mKeyguardFaceAuthInteractor.canFaceAuthRun()) { mUpdateMonitor.requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT, "lockScreenEmptySpaceTap"); @@ -3004,7 +3011,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void setBouncerShowing(boolean bouncerShowing) { mBouncerShowing = bouncerShowing; - mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); + if (!FooterViewRefactor.isEnabled()) { + mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); + } updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 868fbceecafc..dd194eaade9b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -61,7 +61,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.SystemBarUtils; -import com.android.keyguard.FaceAuthApiRequestReason; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; @@ -87,6 +86,8 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -106,12 +107,12 @@ import com.android.systemui.util.kotlin.JavaAdapter; import dalvik.annotation.optimization.NeverCompile; +import dagger.Lazy; + import java.io.PrintWriter; import javax.inject.Inject; -import dagger.Lazy; - /** Handles QuickSettings touch handling, expansion and animation state * TODO (b/264460656) make this dumpable */ @@ -157,6 +158,7 @@ public class QuickSettingsController implements Dumpable { private final InteractionJankMonitor mInteractionJankMonitor; private final ShadeRepository mShadeRepository; private final ShadeInteractor mShadeInteractor; + private final ActiveNotificationsInteractor mActiveNotificationsInteractor; private final JavaAdapter mJavaAdapter; private final FalsingManager mFalsingManager; private final AccessibilityManager mAccessibilityManager; @@ -339,6 +341,7 @@ public class QuickSettingsController implements Dumpable { KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, ShadeRepository shadeRepository, ShadeInteractor shadeInteractor, + ActiveNotificationsInteractor activeNotificationsInteractor, JavaAdapter javaAdapter, CastController castController, SplitShadeStateController splitShadeStateController @@ -386,6 +389,7 @@ public class QuickSettingsController implements Dumpable { mInteractionJankMonitor = interactionJankMonitor; mShadeRepository = shadeRepository; mShadeInteractor = shadeInteractor; + mActiveNotificationsInteractor = activeNotificationsInteractor; mJavaAdapter = javaAdapter; mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback()); @@ -976,14 +980,15 @@ public class QuickSettingsController implements Dumpable { // this will speed up notification actions. if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) { mKeyguardFaceAuthInteractor.onQsExpansionStared(); - mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED); } } void updateQsState() { boolean qsFullScreen = getExpanded() && !mSplitShadeEnabled; mShadeRepository.setLegacyQsFullscreen(qsFullScreen); - mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen); + if (!FooterViewRefactor.isEnabled()) { + mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen); + } mNotificationStackScrollLayoutController.setScrollingEnabled( mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll)); @@ -2230,8 +2235,12 @@ public class QuickSettingsController implements Dumpable { mLockscreenShadeTransitionController.getQSDragProgress()); setExpansionHeight(qsHeight); } - if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0 - && !mMediaDataManager.hasActiveMediaOrRecommendation()) { + + boolean hasNotifications = FooterViewRefactor.isEnabled() + ? mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue() + : mNotificationStackScrollLayoutController.getVisibleNotificationCount() + != 0; + if (!hasNotifications && !mMediaDataManager.hasActiveMediaOrRecommendation()) { // No notifications are visible, let's animate to the height of qs instead if (isQsFragmentCreated()) { // Let's interpolate to the header height instead of the top padding, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java index de334bbd880c..2338be28d32c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java @@ -85,7 +85,9 @@ public class EmptyShadeView extends StackScrollerDecorView { public void setFooterVisibility(@Visibility int visibility) { mFooterVisibility = visibility; - setSecondaryVisible(visibility == View.VISIBLE, false); + setSecondaryVisible(/* visible = */ visibility == View.VISIBLE, + /* animate = */false, + /* onAnimationEnded = */ null); } public void setFooterText(@StringRes int text) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index dd24ca78005f..08415cb5b0cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1033,7 +1033,7 @@ public class KeyguardIndicationController { if (mStatusBarKeyguardViewManager.isBouncerShowing()) { if (mAlternateBouncerInteractor.isVisibleState()) { return; // udfps affordance is highlighted, no need to show action to unlock - } else if (mKeyguardUpdateMonitor.isFaceEnrolled() + } else if (mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled() && !mKeyguardUpdateMonitor.getIsFaceAuthenticated()) { String message; if (mAccessibilityManager.isEnabled() @@ -1215,7 +1215,7 @@ public class KeyguardIndicationController { mContext.getString(R.string.keyguard_suggest_fingerprint) ); } else if (fpAuthFailed - && mKeyguardUpdateMonitor.getUserUnlockedWithFace(getCurrentUser())) { + && mKeyguardUpdateMonitor.isCurrentUserUnlockedWithFace()) { // face had already previously unlocked the device, so instead of showing a // fingerprint error, tell them they have already unlocked with face auth // and how to enter their device diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt index 747efe3fb2c9..933d0ab880bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt @@ -36,7 +36,8 @@ enum class StatusBarMode { /** * A mode where notification icons in the status bar are hidden and replaced by a dot (this mode * can be requested by apps). See - * [com.android.systemui.statusbar.phone.LightsOutNotifController]. + * [com.android.systemui.statusbar.phone.LegacyLightsOutNotifController] and + * [com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor]. */ LIGHTS_OUT, /** Similar to [LIGHTS_OUT], but also with a transparent background for the status bar. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt index d1594ef2e404..04152123e42d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt @@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.merge /** * Repository for data that's specific to the status bar **on keyguard**. For data that applies to - * all status bars, use [StatusBarModeRepository]. + * all status bars, use [StatusBarModeRepositoryStore]. */ interface KeyguardStatusBarRepository { /** True if we can show the user switcher on keyguard and false otherwise. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt index 47994d92d22b..6429815bcb9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt @@ -25,10 +25,8 @@ import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BA import android.view.WindowInsetsController.Appearance import com.android.internal.statusbar.LetterboxDetails import com.android.internal.view.AppearanceRegion -import com.android.systemui.CoreStartable -import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.Dumpable import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.DisplayId import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener import com.android.systemui.statusbar.data.model.StatusBarAppearance @@ -38,13 +36,10 @@ import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator import com.android.systemui.statusbar.phone.StatusBarBoundsProvider import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository -import dagger.Binds -import dagger.Module -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap -import dagger.multibindings.IntoSet +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import java.io.PrintWriter -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -61,7 +56,7 @@ import kotlinx.coroutines.flow.stateIn * Note: These status bar modes are status bar *window* states that are sent to us from * WindowManager, not determined internally. */ -interface StatusBarModeRepository { +interface StatusBarModePerDisplayRepository { /** * True if the status bar window is showing transiently and will disappear soon, and false * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR @@ -108,16 +103,15 @@ interface StatusBarModeRepository { fun clearTransient() } -@SysUISingleton -class StatusBarModeRepositoryImpl -@Inject +class StatusBarModePerDisplayRepositoryImpl +@AssistedInject constructor( @Application scope: CoroutineScope, - @DisplayId thisDisplayId: Int, + @Assisted("displayId") thisDisplayId: Int, private val commandQueue: CommandQueue, private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator, ongoingCallRepository: OngoingCallRepository, -) : StatusBarModeRepository, CoreStartable, OnStatusBarViewInitializedListener { +) : StatusBarModePerDisplayRepository, OnStatusBarViewInitializedListener, Dumpable { private val commandQueueCallback = object : CommandQueue.Callbacks { @@ -166,7 +160,7 @@ constructor( } } - override fun start() { + fun start() { commandQueue.addCallback(commandQueueCallback) } @@ -340,16 +334,7 @@ constructor( ) } -@Module -interface StatusBarModeRepositoryModule { - @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepository - - @Binds - @IntoMap - @ClassKey(StatusBarModeRepositoryImpl::class) - fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable - - @Binds - @IntoSet - fun bindViewInitListener(impl: StatusBarModeRepositoryImpl): OnStatusBarViewInitializedListener +@AssistedFactory +interface StatusBarModePerDisplayRepositoryFactory { + fun create(@Assisted("displayId") displayId: Int): StatusBarModePerDisplayRepositoryImpl } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt new file mode 100644 index 000000000000..962cb0953f97 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.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.statusbar.data.repository + +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.DisplayId +import com.android.systemui.statusbar.core.StatusBarInitializer +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import dagger.multibindings.IntoSet +import java.io.PrintWriter +import javax.inject.Inject + +interface StatusBarModeRepositoryStore { + val defaultDisplay: StatusBarModePerDisplayRepository + fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository +} + +@SysUISingleton +class StatusBarModeRepositoryImpl +@Inject +constructor( + @DisplayId private val displayId: Int, + factory: StatusBarModePerDisplayRepositoryFactory +) : + StatusBarModeRepositoryStore, + CoreStartable, + StatusBarInitializer.OnStatusBarViewInitializedListener { + override val defaultDisplay = factory.create(displayId) + + override fun forDisplay(displayId: Int) = + if (this.displayId == displayId) { + defaultDisplay + } else { + TODO("b/127878649 implement multi-display state management") + } + + override fun start() { + defaultDisplay.start() + } + + override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) { + defaultDisplay.onStatusBarViewInitialized(component) + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + defaultDisplay.dump(pw, args) + } +} + +@Module +interface StatusBarModeRepositoryModule { + @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepositoryStore + + @Binds + @IntoMap + @ClassKey(StatusBarModeRepositoryStore::class) + fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable + + @Binds + @IntoSet + fun bindViewInitListener( + impl: StatusBarModeRepositoryImpl + ): StatusBarInitializer.OnStatusBarViewInitializedListener +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 64970e456c1d..fa2748c1dc77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -16,17 +16,19 @@ package com.android.systemui.statusbar.notification.collection.coordinator +import com.android.app.tracing.traceSection import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.phone.NotificationIconAreaController -import com.android.app.tracing.traceSection import javax.inject.Inject /** @@ -40,6 +42,7 @@ internal constructor( private val groupExpansionManagerImpl: GroupExpansionManagerImpl, private val notificationIconAreaController: NotificationIconAreaController, private val renderListInteractor: RenderNotificationListInteractor, + private val activeNotificationsInteractor: ActiveNotificationsInteractor, ) : Coordinator { override fun attach(pipeline: NotifPipeline) { @@ -49,8 +52,14 @@ internal constructor( fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) = traceSection("StackCoordinator.onAfterRenderList") { - controller.setNotifStats(calculateNotifStats(entries)) - if (NotificationIconContainerRefactor.isEnabled) { + val notifStats = calculateNotifStats(entries) + if (FooterViewRefactor.isEnabled) { + activeNotificationsInteractor.setNotifStats(notifStats) + } + // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the footer + // visibility is handled in the new stack. + controller.setNotifStats(notifStats) + if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) { renderListInteractor.setRenderedList(entries) } else { notificationIconAreaController.updateNotificationIcons(entries) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt index fde4ecb7bcaa..a37937a6c495 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt @@ -26,6 +26,7 @@ interface NotifStackController { /** Data provided to the NotificationRootController whenever the pipeline runs */ data class NotifStats( + // TODO(b/293167744): The count can be removed from here when we remove the FooterView flag. val numActiveNotifs: Int, val hasNonClearableAlertingNotifs: Boolean, val hasClearableAlertingNotifs: Boolean, @@ -33,17 +34,16 @@ data class NotifStats( val hasClearableSilentNotifs: Boolean ) { companion object { - @JvmStatic - val empty = NotifStats(0, false, false, false, false) + @JvmStatic val empty = NotifStats(0, false, false, false, false) } } /** * An implementation of NotifStackController which provides default, no-op implementations of each - * method. This is used by ArcSystemUI so that that implementation can opt-in to overriding - * methods, rather than forcing us to add no-op implementations in their implementation every time - * a method is added. + * method. This is used by ArcSystemUI so that that implementation can opt-in to overriding methods, + * rather than forcing us to add no-op implementations in their implementation every time a method + * is added. */ open class DefaultNotifStackController @Inject constructor() : NotifStackController { override fun setNotifStats(stats: NotifStats) {} -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt index 12ee54d4977d..5ed82cc1ed5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel @@ -37,6 +38,9 @@ class ActiveNotificationListRepository @Inject constructor() { /** Are any already-seen notifications currently filtered out of the active list? */ val hasFilteredOutSeenNotifications = MutableStateFlow(false) + + /** Stats about the list of notifications attached to the shade */ + val notifStats = MutableStateFlow(NotifStats.empty) } /** Represents the notification list, comprised of groups and individual notifications. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 85ba205d3a0a..31893b402e3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -15,17 +15,19 @@ package com.android.systemui.statusbar.notification.domain.interactor +import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map class ActiveNotificationsInteractor @Inject constructor( - repository: ActiveNotificationListRepository, + private val repository: ActiveNotificationListRepository, ) { /** Notifications actively presented to the user in the notification stack, in order. */ val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> = @@ -40,4 +42,25 @@ constructor( } } } + + /** Are any notifications being actively presented in the notification stack? */ + val areAnyNotificationsPresent: Flow<Boolean> = + repository.activeNotifications.map { it.renderList.isNotEmpty() }.distinctUntilChanged() + + /** + * The same as [areAnyNotificationsPresent], but without flows, for easy access in synchronous + * code. + */ + val areAnyNotificationsPresentValue: Boolean + get() = repository.activeNotifications.value.renderList.isNotEmpty() + + /** Are there are any notifications that can be cleared by the "Clear all" button? */ + val hasClearableNotifications: Flow<Boolean> = + repository.notifStats + .map { it.hasClearableAlertingNotifs || it.hasClearableSilentNotifs } + .distinctUntilChanged() + + fun setNotifStats(notifStats: NotifStats) { + repository.notifStats.value = notifStats + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index 10a43d53353d..3184d5efe5cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -46,6 +46,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.util.DumpUtilsKt; import java.io.PrintWriter; +import java.util.function.Consumer; public class FooterView extends StackScrollerDecorView { private static final String TAG = "FooterView"; @@ -63,9 +64,13 @@ public class FooterView extends StackScrollerDecorView { private String mSeenNotifsFilteredText; private Drawable mSeenNotifsFilteredIcon; + private @StringRes int mClearAllButtonTextId; + private @StringRes int mClearAllButtonDescriptionId; private @StringRes int mMessageStringId; private @DrawableRes int mMessageIconId; + private OnClickListener mClearAllButtonClickListener; + public FooterView(Context context, AttributeSet attrs) { super(context, attrs); } @@ -84,12 +89,18 @@ public class FooterView extends StackScrollerDecorView { return isSecondaryVisible(); } + /** See {@link this#setClearAllButtonVisible(boolean, boolean, Consumer)}. */ + public void setClearAllButtonVisible(boolean visible, boolean animate) { + setClearAllButtonVisible(visible, animate, /* onAnimationEnded = */ null); + } + /** * Set the visibility of the "Clear all" button to {@code visible}. Animate the change if * {@code animate} is true. */ - public void setClearAllButtonVisible(boolean visible, boolean animate) { - setSecondaryVisible(visible, animate); + public void setClearAllButtonVisible(boolean visible, boolean animate, + Consumer<Boolean> onAnimationEnded) { + setSecondaryVisible(visible, animate, onAnimationEnded); } @Override @@ -106,6 +117,42 @@ public class FooterView extends StackScrollerDecorView { }); } + /** Set the text label for the "Clear all" button. */ + public void setClearAllButtonText(@StringRes int textId) { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; + if (mClearAllButtonTextId == textId) { + return; // nothing changed + } + mClearAllButtonTextId = textId; + updateClearAllButtonText(); + } + + private void updateClearAllButtonText() { + if (mClearAllButtonTextId == 0) { + return; // not initialized yet + } + mClearAllButton.setText(getContext().getString(mClearAllButtonTextId)); + } + + /** Set the accessibility content description for the "Clear all" button. */ + public void setClearAllButtonDescription(@StringRes int contentDescriptionId) { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + return; + } + if (mClearAllButtonDescriptionId == contentDescriptionId) { + return; // nothing changed + } + mClearAllButtonDescriptionId = contentDescriptionId; + updateClearAllButtonDescription(); + } + + private void updateClearAllButtonDescription() { + if (mClearAllButtonDescriptionId == 0) { + return; // not initialized yet + } + mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId)); + } + /** Set the string for a message to be shown instead of the buttons. */ public void setMessageString(@StringRes int messageId) { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; @@ -181,6 +228,10 @@ public class FooterView extends StackScrollerDecorView { /** Set onClickListener for the clear all (end) button. */ public void setClearAllButtonClickListener(OnClickListener listener) { + if (FooterViewRefactor.isEnabled()) { + if (mClearAllButtonClickListener == listener) return; + mClearAllButtonClickListener = listener; + } mClearAllButton.setOnClickListener(listener); } @@ -214,7 +265,28 @@ public class FooterView extends StackScrollerDecorView { mManageButton.setText(mManageNotificationText); mManageButton.setContentDescription(mManageNotificationText); } - if (!FooterViewRefactor.isEnabled()) { + if (FooterViewRefactor.isEnabled()) { + updateClearAllButtonText(); + updateClearAllButtonDescription(); + + updateMessageString(); + updateMessageIcon(); + } else { + // NOTE: Prior to the refactor, `updateResources` set the class properties to the right + // string values. It was always being called together with `updateContent`, which + // deals with actually associating those string values with the correct views + // (buttons or text). + // In the new code, the resource IDs are being set in the view binder (through + // setMessageString and similar setters). The setters themselves now deal with + // updating both the resource IDs and the views where appropriate (as in, calling + // `updateMessageString` when the resource ID changes). This eliminates the need for + // `updateResources`, which will eventually be removed. There are, however, still + // situations in which we want to update the views even if the resource IDs didn't + // change, such as configuration changes. + mClearAllButton.setText(R.string.clear_all_notifications_text); + mClearAllButton.setContentDescription( + mContext.getString(R.string.accessibility_clear_all)); + mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText); mSeenNotifsFooterTextView .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null); @@ -230,16 +302,8 @@ public class FooterView extends StackScrollerDecorView { protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); updateColors(); - mClearAllButton.setText(R.string.clear_all_notifications_text); - mClearAllButton.setContentDescription( - mContext.getString(R.string.accessibility_clear_all)); updateResources(); updateContent(); - - if (FooterViewRefactor.isEnabled()) { - updateMessageString(); - updateMessageIcon(); - } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt index 6d8234371b65..0299114e0afc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt @@ -16,10 +16,14 @@ package com.android.systemui.statusbar.notification.footer.ui.viewbinder +import android.view.View import androidx.lifecycle.lifecycleScope import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel +import com.android.systemui.util.ui.isAnimating +import com.android.systemui.util.ui.stopAnimating +import com.android.systemui.util.ui.value import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.launch @@ -28,9 +32,31 @@ object FooterViewBinder { fun bind( footer: FooterView, viewModel: FooterViewModel, + clearAllNotifications: View.OnClickListener, ): DisposableHandle { + // Listen for changes when the view is attached. return footer.repeatWhenAttached { - // Listen for changes when the view is attached. + lifecycleScope.launch { + viewModel.clearAllButton.collect { button -> + if (button.isVisible.isAnimating) { + footer.setClearAllButtonVisible( + button.isVisible.value, + /* animate = */ true, + ) { _ -> + button.isVisible.stopAnimating() + } + } else { + footer.setClearAllButtonVisible( + button.isVisible.value, + /* animate = */ false, + ) + } + footer.setClearAllButtonText(button.labelId) + footer.setClearAllButtonDescription(button.accessibilityDescriptionId) + footer.setClearAllButtonClickListener(clearAllNotifications) + } + } + lifecycleScope.launch { viewModel.message.collect { message -> footer.setFooterLabelVisible(message.visible) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt new file mode 100644 index 000000000000..ea5abeff7042 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.footer.ui.viewmodel + +import android.annotation.StringRes +import com.android.systemui.util.ui.AnimatedValue + +data class FooterButtonViewModel( + @StringRes val labelId: Int, + @StringRes val accessibilityDescriptionId: Int, + val isVisible: AnimatedValue<Boolean>, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt index 739048531afb..721bea1086e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt @@ -18,21 +18,50 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.view.FooterView +import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.ui.AnimatableEvent +import com.android.systemui.util.ui.toAnimatedValueFlow import dagger.Module import dagger.Provides import java.util.Optional import javax.inject.Provider import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart /** ViewModel for [FooterView]. */ -class FooterViewModel(seenNotificationsInteractor: SeenNotificationsInteractor) { - init { - /* Check if */ FooterViewRefactor.isUnexpectedlyInLegacyMode() - } +class FooterViewModel( + activeNotificationsInteractor: ActiveNotificationsInteractor, + seenNotificationsInteractor: SeenNotificationsInteractor, + shadeInteractor: ShadeInteractor, +) { + val clearAllButton: Flow<FooterButtonViewModel> = + activeNotificationsInteractor.hasClearableNotifications + .sample( + combine( + shadeInteractor.isShadeFullyExpanded, + shadeInteractor.isShadeTouchable, + ::Pair + ) + .onStart { emit(Pair(false, false)) } + ) { hasClearableNotifications, (isShadeFullyExpanded, animationsEnabled) -> + val shouldAnimate = isShadeFullyExpanded && animationsEnabled + AnimatableEvent(hasClearableNotifications, shouldAnimate) + } + .toAnimatedValueFlow() + .map { visible -> + FooterButtonViewModel( + labelId = R.string.clear_all_notifications_text, + accessibilityDescriptionId = R.string.accessibility_clear_all, + isVisible = visible, + ) + } val message: Flow<FooterMessageViewModel> = seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs -> @@ -49,10 +78,18 @@ object FooterViewModelModule { @Provides @SysUISingleton fun provideOptional( + activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>, seenNotificationsInteractor: Provider<SeenNotificationsInteractor>, + shadeInteractor: Provider<ShadeInteractor>, ): Optional<FooterViewModel> { return if (FooterViewRefactor.isEnabled) { - Optional.of(FooterViewModel(seenNotificationsInteractor.get())) + Optional.of( + FooterViewModel( + activeNotificationsInteractor.get(), + seenNotificationsInteractor.get(), + shadeInteractor.get() + ) + ) } else { Optional.empty() } 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 9f2b0a6bfc1f..8e824423973f 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 @@ -204,6 +204,11 @@ class BubbleNotAllowedSuppressor() : override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble() } +class BubbleAppSuspendedSuppressor : + VisualInterruptionFilter(types = setOf(BUBBLE), reason = "app is suspended") { + override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.isSuspended +} + class BubbleNoMetadataSuppressor() : VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 2fffd379e6f9..334e08d5a78f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -56,6 +56,14 @@ class NotificationInterruptLogger @Inject constructor( } } + fun logSuspendedAppBubble(entry: NotificationEntry) { + buffer.log(TAG, DEBUG, { + str1 = entry.logKey + }, { + "No bubble up: notification: app $str1 is suspended" + }) + } + fun logNoBubbleNoMetadata(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { str1 = entry.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 40453800d17f..510086d4892b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -204,6 +204,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } + if (entry.getRanking().isSuspended()) { + mLogger.logSuspendedAppBubble(entry); + return false; + } + if (entry.getBubbleMetadata() == null || (entry.getBubbleMetadata().getShortcutId() == null && entry.getBubbleMetadata().getIntent() == null)) { 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 39a87de89330..73dd5f6e5fb6 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 @@ -159,6 +159,7 @@ constructor( addFilter(PulseLockscreenVisibilityPrivateSuppressor()) addFilter(PulseLowImportanceSuppressor()) addFilter(BubbleNotAllowedSuppressor()) + addFilter(BubbleAppSuspendedSuppressor()) addFilter(BubbleNoMetadataSuppressor()) addFilter(HunGroupAlertBehaviorSuppressor()) addFilter(HunJustLaunchedFsiSuppressor()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 11c65e542bcd..6cb079a22e7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2221,8 +2221,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mMenuRow != null) { mMenuRow.resetMenu(); } - mTranslateAnim = null; } + mTranslateAnim = null; } }); mTranslateAnim = translateAnim; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index 8eda96f62257..64f61d9ac2da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -27,6 +27,7 @@ import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import androidx.annotation.NonNull; @@ -42,9 +43,11 @@ import java.util.Arrays; * A view that can be used for both the dimmed and normal background of an notification. */ public class NotificationBackgroundView extends View implements Dumpable { + private static final String TAG = "NotificationBackgroundView"; private final boolean mDontModifyCorners; private Drawable mBackground; + private Drawable mBackgroundDrawableToTint; private int mClipTopAmount; private int mClipBottomAmount; private int mTintColor; @@ -131,6 +134,7 @@ public class NotificationBackgroundView extends View implements Dumpable { unscheduleDrawable(mBackground); } mBackground = background; + mBackgroundDrawableToTint = findBackgroundDrawableToTint(mBackground); mRippleColor = null; mBackground.mutate(); if (mBackground != null) { @@ -144,25 +148,46 @@ public class NotificationBackgroundView extends View implements Dumpable { invalidate(); } + // setCustomBackground should be called from ActivatableNotificationView.initBackground + // with R.drawable.notification_material_bg, which is a layer-list with a lower layer + // for the background color (annotated with an ID so we can find it) and an upper layer + // to blend in the stateful @color/notification_overlay_color. + // + // If the notification is tinted, we want to set a tint list on *just that lower layer* that + // will replace the default materialColorSurfaceContainerHigh *without* wiping out the stateful + // tints in the upper layer that make the hovered and pressed states visible. + // + // This function fishes that lower layer out, or makes a fuss in logcat if it can't find it. + private @Nullable Drawable findBackgroundDrawableToTint(@Nullable Drawable background) { + if (background == null) { + return null; + } + + if (!(background instanceof LayerDrawable)) { + Log.wtf(TAG, "background is not a LayerDrawable: " + background); + return background; + } + + final Drawable backgroundColorLayer = ((LayerDrawable) background).findDrawableByLayerId( + R.id.notification_background_color_layer); + + if (backgroundColorLayer == null) { + Log.wtf(TAG, "background is missing background color layer: " + background); + return background; + } + + return backgroundColorLayer; + } + public void setCustomBackground(int drawableResId) { final Drawable d = mContext.getDrawable(drawableResId); setCustomBackground(d); } public void setTint(int tintColor) { - if (tintColor != 0) { - ColorStateList stateList = new ColorStateList(new int[][]{ - new int[]{com.android.internal.R.attr.state_pressed}, - new int[]{com.android.internal.R.attr.state_hovered}, - new int[]{}}, - - new int[]{tintColor, tintColor, tintColor} - ); - mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP); - mBackground.setTintList(stateList); - } else { - mBackground.setTintList(null); - } + mBackgroundDrawableToTint.setTint(tintColor); + mBackgroundDrawableToTint.setTintMode(PorterDuff.Mode.SRC_ATOP); + mTintColor = tintColor; invalidate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index ec90a8d6ad59..162e8af47394 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -54,7 +54,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { mContent = findContentView(); mSecondaryView = findSecondaryView(); setVisible(false /* visible */, false /* animate */); - setSecondaryVisible(false /* visible */, false /* animate */); + setSecondaryVisible(false /* visible */, false /* animate */, null /* onAnimationEnd */); setOutlineProvider(null); } @@ -155,15 +155,23 @@ public abstract class StackScrollerDecorView extends ExpandableView { /** * Set the secondary view of this layout to visible. * - * @param visible should the secondary view be visible - * @param animate should the change be animated + * @param visible True if the contents should be visible. + * @param animate True if we should fade to new visibility. + * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a + * parameter that represents whether the animation was cancelled. */ - protected void setSecondaryVisible(boolean visible, boolean animate) { + protected void setSecondaryVisible(boolean visible, boolean animate, + Consumer<Boolean> onAnimationEnded) { if (mIsSecondaryVisible != visible) { mSecondaryAnimating = animate; mIsSecondaryVisible = visible; - setViewVisible(mSecondaryView, visible, animate, - (cancelled) -> onSecondaryVisibilityAnimationEnd()); + Consumer<Boolean> onAnimationEndedWrapper = (cancelled) -> { + onContentVisibilityAnimationEnd(); + if (onAnimationEnded != null) { + onAnimationEnded.accept(cancelled); + } + }; + setViewVisible(mSecondaryView, visible, animate, onAnimationEndedWrapper); } if (!mSecondaryAnimating) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataStoreRefactor.kt index 44387c225ef1..8fc7106ecada 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataStoreRefactor.kt @@ -50,4 +50,4 @@ object NotificationsLiveDataStoreRefactor { */ @JvmStatic inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -}
\ No newline at end of file +} 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 38d782b43c16..6944453506a8 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 @@ -565,6 +565,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private NotificationStackScrollLayoutController.TouchHandler mTouchHandler; private final ScreenOffAnimationController mScreenOffAnimationController; private boolean mShouldUseSplitNotificationShade; + private boolean mShouldSkipTopPaddingAnimationAfterFold = false; private boolean mHasFilteredOutSeenNotifications; @Nullable private SplitShadeStateController mSplitShadeStateController = null; private boolean mIsSmallLandscapeLockscreenEnabled = false; @@ -740,7 +741,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateFooter(); } - void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) { + /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */ + public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) { + FooterViewRefactor.assertInLegacyMode(); mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications; } @@ -749,7 +752,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mFooterView == null || mController == null) { return; } - // TODO: move this logic to controller, which will invoke updateFooterView directly final boolean showHistory = mController.isHistoryEnabled(); final boolean showDismissView = shouldShowDismissView(); @@ -773,13 +775,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable && !mIsRemoteInputActive; } - /** - * Return whether there are any clearable notifications - */ - boolean hasActiveClearableNotifications(@SelectedRows int selection) { - return mController.hasActiveClearableNotifications(selection); - } - public NotificationSwipeActionHelper getSwipeActionHelper() { return mSwipeHelper; } @@ -1370,7 +1365,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mTopPadding = topPadding; updateAlgorithmHeightAndPadding(); updateContentHeight(); - if (shouldAnimate && mAnimationsEnabled && mIsExpanded) { + if (mAmbientState.isOnKeyguard() + && !mShouldUseSplitNotificationShade + && mShouldSkipTopPaddingAnimationAfterFold) { + mShouldSkipTopPaddingAnimationAfterFold = false; + } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) { mTopPaddingNeedsAnimation = true; mNeedsAnimation = true; } @@ -1658,8 +1657,44 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * @return the position from where the appear transition ends when expanding. * Measured in absolute height. + * + * TODO(b/308591475): This entire logic can probably be improved as part of the empty shade + * refactor, but for now: + * - if the empty shade is visible, we can assume that the visible notif count is not 0; + * - if the shelf is visible, we can assume there are at least 2 notifications present (this + * is not true for AOD, but this logic refers to the expansion of the shade, where we never + * have the shelf on its own) */ private float getAppearEndPosition() { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + return getAppearEndPositionLegacy(); + } + + int appearPosition = mAmbientState.getStackTopMargin(); + if (mEmptyShadeView.getVisibility() == GONE) { + if (isHeadsUpTransition() + || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) { + if (mShelf.getVisibility() != GONE) { + appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements; + } + appearPosition += getTopHeadsUpPinnedHeight() + + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow()); + } else if (mShelf.getVisibility() != GONE) { + appearPosition += mShelf.getIntrinsicHeight(); + } + } else { + appearPosition = mEmptyShadeView.getHeight(); + } + return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); + } + + /** + * The version of {@code getAppearEndPosition} that uses the notif count. The view shouldn't + * need to know about that, so we want to phase this out with the footer view refactor. + */ + private float getAppearEndPositionLegacy() { + FooterViewRefactor.assertInLegacyMode(); + int appearPosition = mAmbientState.getStackTopMargin(); int visibleNotifCount = mController.getVisibleNotificationCount(); if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) { @@ -1698,7 +1733,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // This can't use expansion fraction as that goes only from 0 to 1. Also when // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3 // and that makes translation jump immediately. - float appearEndPosition = getAppearEndPosition(); + float appearEndPosition = FooterViewRefactor.isEnabled() ? getAppearEndPosition() + : getAppearEndPositionLegacy(); float appearStartPosition = getAppearStartPosition(); float hunAppearFraction = (height - appearStartPosition) / (appearEndPosition - appearStartPosition); @@ -3720,6 +3756,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } protected boolean isInsideQsHeader(MotionEvent ev) { + if (mQsHeader == null) { + Log.wtf(TAG, "qsHeader is null while NSSL is handling a touch"); + return false; + } + mQsHeader.getBoundsOnScreen(mQsHeaderBound); /** * One-handed mode defines a feature FEATURE_ONE_HANDED of DisplayArea {@link DisplayArea} @@ -4577,13 +4618,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mManageButtonClickListener != null) { mFooterView.setManageButtonClickListener(mManageButtonClickListener); } - mFooterView.setClearAllButtonClickListener(v -> { - if (mFooterClearAllListener != null) { - mFooterClearAllListener.onClearAll(); - } - clearNotifications(ROWS_ALL, true /* closeShade */); - footerView.setClearAllButtonVisible(false /* visible */, true /* animate */); - }); + if (!FooterViewRefactor.isEnabled()) { + mFooterView.setClearAllButtonClickListener(v -> { + if (mFooterClearAllListener != null) { + mFooterClearAllListener.onClearAll(); + } + clearNotifications(ROWS_ALL, true /* closeShade */); + footerView.setClearAllButtonVisible(false /* visible */, true /* animate */); + }); + } if (FooterViewRefactor.isEnabled()) { updateFooter(); } @@ -4599,12 +4642,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable addView(mEmptyShadeView, index); } - void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) { + /** Legacy version, should be removed with the footer refactor flag. */ + public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) { + FooterViewRefactor.assertInLegacyMode(); + updateEmptyShadeView(visible, areNotificationsHiddenInShade, + mHasFilteredOutSeenNotifications); + } + + /** Trigger an update for the empty shade resources and visibility. */ + public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade, + boolean hasFilteredOutSeenNotifications) { mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled); if (areNotificationsHiddenInShade) { updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0); - } else if (mHasFilteredOutSeenNotifications) { + } else if (hasFilteredOutSeenNotifications) { updateEmptyShadeView( R.string.no_unseen_notif_text, R.string.unlock_to_see_notif_text, @@ -4647,9 +4699,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } boolean animate = mIsExpanded && mAnimationsEnabled; mFooterView.setVisible(visible, animate); - mFooterView.setClearAllButtonVisible(showDismissView, animate); mFooterView.showHistory(showHistory); if (!FooterViewRefactor.isEnabled()) { + mFooterView.setClearAllButtonVisible(showDismissView, animate); mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications); } } @@ -4781,10 +4833,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void removeContainerView(View v) { Assert.isMainThread(); removeView(v); - if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) { - mController.updateShowEmptyShadeView(); - updateFooter(); - mController.updateImportantForAccessibility(); + if (!FooterViewRefactor.isEnabled()) { + // A notification was removed, and we're not currently showing the empty shade view. + if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) { + mController.updateShowEmptyShadeView(); + updateFooter(); + mController.updateImportantForAccessibility(); + } } updateSpeedBumpIndex(); @@ -4793,10 +4848,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void addContainerView(View v) { Assert.isMainThread(); addView(v); - if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { - mController.updateShowEmptyShadeView(); - updateFooter(); - mController.updateImportantForAccessibility(); + if (!FooterViewRefactor.isEnabled()) { + // A notification was added, and we're currently showing the empty shade view. + if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { + mController.updateShowEmptyShadeView(); + updateFooter(); + mController.updateImportantForAccessibility(); + } } updateSpeedBumpIndex(); @@ -4806,7 +4864,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable Assert.isMainThread(); ensureRemovedFromTransientContainer(v); addView(v, index); - if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { + // A notification was added, and we're currently showing the empty shade view. + if (!FooterViewRefactor.isEnabled() && v instanceof ExpandableNotificationRow + && mController.isShowingEmptyShadeView()) { mController.updateShowEmptyShadeView(); updateFooter(); mController.updateImportantForAccessibility(); @@ -5079,7 +5139,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mEmptyShadeView.getVisibility() == GONE) { return getMinExpansionHeight(); } else { - return getAppearEndPosition(); + return FooterViewRefactor.isEnabled() ? getAppearEndPosition() + : getAppearEndPositionLegacy(); } } @@ -5306,11 +5367,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return viewsToRemove; } + /** Clear all clearable notifications when the user requests it. */ + public void clearAllNotifications() { + clearNotifications(ROWS_ALL, /* closeShade = */ true); + } + /** * Collects a list of visible rows, and animates them away in a staggered fashion as if they * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd. */ - @VisibleForTesting void clearNotifications(@SelectedRows int selection, boolean closeShade) { // Animate-swipe all dismissable notifications, then animate the shade closed final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection); @@ -5610,6 +5675,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void setFooterClearAllListener(FooterClearAllListener listener) { + FooterViewRefactor.assertInLegacyMode(); mFooterClearAllListener = listener; } @@ -5680,6 +5746,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable boolean split = mSplitShadeStateController.shouldUseSplitNotificationShade(getResources()); if (split != mShouldUseSplitNotificationShade) { mShouldUseSplitNotificationShade = split; + mShouldSkipTopPaddingAnimationAfterFold = true; mAmbientState.setUseSplitShade(split); updateDismissBehavior(); updateUseRoundedRectClipping(); 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 3e140a45f8b9..e6315fd159d5 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 @@ -20,7 +20,6 @@ import static android.service.notification.NotificationStats.DISMISSAL_SHADE; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; import static com.android.app.animation.Interpolators.STANDARD; - import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; @@ -108,7 +107,9 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.SilentHeader; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -223,7 +224,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onViewAttachedToWindow(View v) { mConfigurationController.addCallback(mConfigurationListener); - mZenModeController.addCallback(mZenModeControllerCallback); + if (!FooterViewRefactor.isEnabled()) { + mZenModeController.addCallback(mZenModeControllerCallback); + } final int newBarState = mStatusBarStateController.getState(); if (newBarState != mBarState) { mStateListener.onStateChanged(newBarState); @@ -236,7 +239,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onViewDetachedFromWindow(View v) { mConfigurationController.removeCallback(mConfigurationListener); - mZenModeController.removeCallback(mZenModeControllerCallback); + if (!FooterViewRefactor.isEnabled()) { + mZenModeController.removeCallback(mZenModeControllerCallback); + } mStatusBarStateController.removeCallback(mStateListener); } }; @@ -292,7 +297,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { final ConfigurationListener mConfigurationListener = new ConfigurationListener() { @Override public void onDensityOrFontScaleChanged() { - updateShowEmptyShadeView(); + if (!FooterViewRefactor.isEnabled()) { + updateShowEmptyShadeView(); + } mView.reinflateViews(); } @@ -308,7 +315,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.updateBgColor(); mView.updateDecorViews(); mView.reinflateViews(); - updateShowEmptyShadeView(); + if (!FooterViewRefactor.isEnabled()) { + updateShowEmptyShadeView(); + } updateFooter(); } @@ -357,7 +366,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(), mLockscreenUserManager.isAnyProfilePublicMode()); mView.onStatePostChange(mStatusBarStateController.fromShadeLocked()); - updateImportantForAccessibility(); + if (!FooterViewRefactor.isEnabled()) { + updateImportantForAccessibility(); + } } }; @@ -459,7 +470,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onSnooze(StatusBarNotification sbn, - NotificationSwipeActionHelper.SnoozeOption snoozeOption) { + NotificationSwipeActionHelper.SnoozeOption snoozeOption) { mNotificationsController.setNotificationSnoozed(sbn, snoozeOption); } @@ -581,7 +592,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public boolean updateSwipeProgress(View animView, boolean dismissable, - float swipeProgress) { + float swipeProgress) { // Returning true prevents alpha fading. return false; } @@ -673,6 +684,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { UiEventLogger uiEventLogger, NotificationRemoteInputManager remoteInputManager, VisibilityLocationProviderDelegator visibilityLocationProviderDelegator, + ActiveNotificationsInteractor activeNotificationsInteractor, SeenNotificationsInteractor seenNotificationsInteractor, NotificationListViewBinder viewBinder, ShadeController shadeController, @@ -747,8 +759,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setClearAllAnimationListener(this::onAnimationEnd); mView.setClearAllListener((selection) -> mUiEventLogger.log( NotificationPanelEvent.fromSelection(selection))); - mView.setFooterClearAllListener(() -> - mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); + if (!FooterViewRefactor.isEnabled()) { + mView.setFooterClearAllListener(() -> + mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); + } mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive()); mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() { @Override @@ -840,8 +854,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { mViewBinder.bind(mView, this); - collectFlow(mView, mKeyguardTransitionRepo.getTransitions(), - this::onKeyguardTransitionChanged); + if (!FooterViewRefactor.isEnabled()) { + collectFlow(mView, mKeyguardTransitionRepo.getTransitions(), + this::onKeyguardTransitionChanged); + } } private boolean isInVisibleLocation(NotificationEntry entry) { @@ -1031,6 +1047,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public int getVisibleNotificationCount() { + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle footer + // visibility in the refactored code return mNotifStats.getNumActiveNotifs(); } @@ -1074,7 +1092,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void setOverScrollAmount(float amount, boolean onTop, boolean animate, - boolean cancelAnimators) { + boolean cancelAnimators) { mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators); } @@ -1124,6 +1142,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void setQsFullScreen(boolean fullScreen) { + FooterViewRefactor.assertInLegacyMode(); mView.setQsFullScreen(fullScreen); updateShowEmptyShadeView(); } @@ -1273,7 +1292,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void updateVisibility(boolean visible) { mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - if (mView.getVisibility() == View.VISIBLE) { + // Refactor note: the empty shade's visibility doesn't seem to actually depend on the + // parent visibility (so this update seemingly doesn't do anything). Therefore, this is not + // modeled in the refactored code. + if (!FooterViewRefactor.isEnabled() && mView.getVisibility() == View.VISIBLE) { // Synchronize EmptyShadeView visibility with the parent container. updateShowEmptyShadeView(); updateImportantForAccessibility(); @@ -1288,6 +1310,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { * are true. */ public void updateShowEmptyShadeView() { + FooterViewRefactor.assertInLegacyMode(); + Trace.beginSection("NSSLC.updateShowEmptyShadeView"); final boolean shouldShow = getVisibleNotificationCount() == 0 @@ -1331,6 +1355,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { * auto-scrolling in NSSL. */ public void updateImportantForAccessibility() { + FooterViewRefactor.assertInLegacyMode(); if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) { mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); } else { @@ -1338,16 +1363,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } } - /** - * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD - * and false otherwise. - */ - private boolean isInTransitionToKeyguard() { - final int currentState = mStatusBarStateController.getState(); - final int upcomingState = mStatusBarStateController.getCurrentOrUpcomingState(); - return (currentState != upcomingState && upcomingState == KEYGUARD); - } - public boolean isShowingEmptyShadeView() { return mView.isEmptyShadeViewVisible(); } @@ -1395,10 +1410,14 @@ public class NotificationStackScrollLayoutController implements Dumpable { * Return whether there are any clearable notifications */ public boolean hasActiveClearableNotifications(@SelectedRows int selection) { + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer + // visibility in the refactored code return hasNotifications(selection, true /* clearable */); } public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer + // visibility in the refactored code boolean hasAlertingMatchingClearable = isClearable ? mNotifStats.getHasClearableAlertingNotifs() : mNotifStats.getHasNonClearableAlertingNotifs(); @@ -1437,7 +1456,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { public RemoteInputController.Delegate createDelegate() { return new RemoteInputController.Delegate() { public void setRemoteInputActive(NotificationEntry entry, - boolean remoteInputActive) { + boolean remoteInputActive) { mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); entry.notifyHeightChanged(true /* needsAnimation */); updateFooter(); @@ -1556,7 +1575,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove, - @SelectedRows int selectedRows) { + @SelectedRows int selectedRows) { if (selectedRows == ROWS_ALL) { mNotifCollection.dismissAllNotifications( mLockscreenUserManager.getCurrentUserId()); @@ -1648,7 +1667,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { * Set rounded rect clipping bounds on this view. */ public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, - int bottomRadius) { + int bottomRadius) { mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius); } @@ -1678,6 +1697,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @VisibleForTesting void onKeyguardTransitionChanged(TransitionStep transitionStep) { + FooterViewRefactor.assertInLegacyMode(); boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD) && (transitionStep.getFrom().equals(KeyguardState.GONE) || transitionStep.getFrom().equals(KeyguardState.OCCLUDED)); @@ -2003,11 +2023,21 @@ public class NotificationStackScrollLayoutController implements Dumpable { private class NotifStackControllerImpl implements NotifStackController { @Override public void setNotifStats(@NonNull NotifStats notifStats) { + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once footer visibility + // is handled in the refactored stack. mNotifStats = notifStats; - mView.setHasFilteredOutSeenNotifications( - mSeenNotificationsInteractor.getHasFilteredOutSeenNotifications().getValue()); + + if (!FooterViewRefactor.isEnabled()) { + mView.setHasFilteredOutSeenNotifications( + mSeenNotificationsInteractor + .getHasFilteredOutSeenNotifications().getValue()); + } + updateFooter(); - updateShowEmptyShadeView(); + + if (!FooterViewRefactor.isEnabled()) { + updateShowEmptyShadeView(); + } } } } 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 a5b87f088578..4554085c35c0 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 @@ -17,9 +17,13 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import android.view.LayoutInflater +import androidx.lifecycle.lifecycleScope import com.android.app.tracing.traceSection +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.nano.MetricsProto import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.reinflateAndBindLatest +import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R @@ -36,12 +40,15 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.policy.ConfigurationController import javax.inject.Inject +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch /** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */ class NotificationListViewBinder @Inject constructor( private val viewModel: NotificationListViewModel, + private val metricsLogger: MetricsLogger, private val configuration: ConfigurationState, private val configurationController: ConfigurationController, private val falsingManager: FalsingManager, @@ -56,7 +63,16 @@ constructor( ) { bindShelf(view) bindFooter(view) + bindEmptyShade(view) bindHideList(viewController, viewModel) + + view.repeatWhenAttached { + lifecycleScope.launch { + viewModel.isImportantForAccessibility.collect { isImportantForAccessibility -> + view.setImportantForAccessibilityYesNo(isImportantForAccessibility) + } + } + } } private fun bindShelf(parentView: NotificationStackScrollLayout) { @@ -87,7 +103,17 @@ constructor( attachToRoot = false, ) { footerView: FooterView -> traceSection("bind FooterView") { - val disposableHandle = FooterViewBinder.bind(footerView, footerViewModel) + val disposableHandle = + FooterViewBinder.bind( + footerView, + footerViewModel, + clearAllNotifications = { + metricsLogger.action( + MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES + ) + parentView.clearAllNotifications() + }, + ) parentView.setFooterView(footerView) return@reinflateAndBindLatest disposableHandle } @@ -95,4 +121,26 @@ constructor( } } } + + private fun bindEmptyShade( + parentView: NotificationStackScrollLayout, + ) { + parentView.repeatWhenAttached { + lifecycleScope.launch { + combine( + viewModel.shouldShowEmptyShadeView, + viewModel.areNotificationsHiddenInShade, + viewModel.hasFilteredOutSeenNotifications, + ::Triple + ) + .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) -> + parentView.updateEmptyShadeView( + shouldShow, + areNotifsHidden, + hasFilteredNotifs, + ) + } + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index 4f7668060c69..569ae248ad23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -16,10 +16,22 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel +import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import java.util.Optional import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.onStart /** ViewModel for the list of notifications. */ class NotificationListViewModel @@ -27,5 +39,78 @@ class NotificationListViewModel constructor( val shelf: NotificationShelfViewModel, val hideListViewModel: HideListViewModel, - val footer: Optional<FooterViewModel> -) + val footer: Optional<FooterViewModel>, + activeNotificationsInteractor: ActiveNotificationsInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, + seenNotificationsInteractor: SeenNotificationsInteractor, + shadeInteractor: ShadeInteractor, + zenModeInteractor: ZenModeInteractor, +) { + /** + * We want the NSSL to be unimportant for accessibility when there are no notifications in it + * while the device is on lock screen, to avoid an unlabelled NSSL view in TalkBack. Otherwise, + * we want it to be important for accessibility to enable accessibility auto-scrolling in NSSL. + * See b/242235264 for more details. + */ + val isImportantForAccessibility: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(true) + } else { + combine( + activeNotificationsInteractor.areAnyNotificationsPresent, + keyguardTransitionInteractor.isFinishedInStateWhere { + KeyguardState.lockscreenVisibleInState(it) + } + ) { hasNotifications, isOnKeyguard -> + hasNotifications || !isOnKeyguard + } + .distinctUntilChanged() + } + } + + val shouldShowEmptyShadeView: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + combine( + activeNotificationsInteractor.areAnyNotificationsPresent, + shadeInteractor.isQsFullscreen, + keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD).onStart { + emit(false) + }, + keyguardTransitionInteractor + .isFinishedInState(KeyguardState.PRIMARY_BOUNCER) + .onStart { emit(false) } + ) { hasNotifications, isQsFullScreen, transitioningToAOD, isBouncerShowing -> + !hasNotifications && + !isQsFullScreen && + // Hide empty shade view when in transition to AOD. + // That avoids "No Notifications" blinking when transitioning to AOD. + // For more details, see b/228790482. + !transitioningToAOD && + // Don't show any notification content if the bouncer is showing. See + // b/267060171. + !isBouncerShowing + } + .distinctUntilChanged() + } + } + + // TODO(b/308591475): This should be tracked separately by the empty shade. + val areNotificationsHiddenInShade: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + zenModeInteractor.areNotificationsHiddenInShade + } + } + + // TODO(b/308591475): This should be tracked separately by the empty shade. + val hasFilteredOutSeenNotifications: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + seenNotificationsInteractor.hasFilteredOutSeenNotifications + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index e1fba2eda1c4..7aa7976b8f92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -372,26 +372,6 @@ constructor( ) } - override fun executeRunnableDismissingKeyguard( - runnable: Runnable?, - cancelAction: Runnable?, - dismissShade: Boolean, - afterKeyguardGone: Boolean, - deferred: Boolean, - willAnimateOnKeyguard: Boolean, - customMessage: String?, - ) { - activityStarterInternal.executeRunnableDismissingKeyguard( - runnable = runnable, - cancelAction = cancelAction, - dismissShade = dismissShade, - afterKeyguardGone = afterKeyguardGone, - deferred = deferred, - willAnimateOnKeyguard = willAnimateOnKeyguard, - customMessage = customMessage, - ) - } - override fun postQSRunnableDismissingKeyguard(runnable: Runnable?) { postOnUiThread { statusBarStateController.setLeaveOpenOnKeyguardHide(true) 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 645769cdd586..57d49b250883 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -33,7 +33,6 @@ import static com.android.systemui.statusbar.NotificationLockscreenUserManager.P import static com.android.systemui.statusbar.StatusBarState.SHADE; import android.annotation.Nullable; -import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.IWallpaperManager; import android.app.KeyguardManager; @@ -200,7 +199,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.core.StatusBarInitializer; import com.android.systemui.statusbar.data.model.StatusBarMode; -import com.android.systemui.statusbar.data.repository.StatusBarModeRepository; +import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; @@ -388,7 +387,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarInitializer mStatusBarInitializer; private final StatusBarWindowController mStatusBarWindowController; - private final StatusBarModeRepository mStatusBarModeRepository; + private final StatusBarModeRepositoryStore mStatusBarModeRepository; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @VisibleForTesting DozeServiceHost mDozeServiceHost; @@ -606,7 +605,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { StatusBarInitializer statusBarInitializer, StatusBarWindowController statusBarWindowController, StatusBarWindowStateController statusBarWindowStateController, - StatusBarModeRepository statusBarModeRepository, + StatusBarModeRepositoryStore statusBarModeRepository, KeyguardUpdateMonitor keyguardUpdateMonitor, StatusBarSignalPolicy statusBarSignalPolicy, PulseExpansionHandler pulseExpansionHandler, @@ -900,7 +899,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { setUpPresenter(); if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) { - mStatusBarModeRepository.showTransient(); + mStatusBarModeRepository.getDefaultDisplay().showTransient(); } mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance, result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior, @@ -1147,9 +1146,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mDemoModeController.addCallback(mDemoModeCallback); mJavaAdapter.alwaysCollectFlow( - mStatusBarModeRepository.isTransientShown(), this::onTransientShownChanged); + mStatusBarModeRepository.getDefaultDisplay().isTransientShown(), + this::onTransientShownChanged); mJavaAdapter.alwaysCollectFlow( - mStatusBarModeRepository.getStatusBarMode(), + mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode(), this::updateBarMode); mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get(); @@ -1209,7 +1209,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void hide() { - mStatusBarModeRepository.clearTransient(); + mStatusBarModeRepository.getDefaultDisplay().clearTransient(); } }); @@ -1657,7 +1657,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (mDemoModeController.isInDemoMode()) return; if (mStatusBarTransitions != null) { checkBarMode( - mStatusBarModeRepository.getStatusBarMode().getValue(), + mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode().getValue(), mStatusBarWindowState, mStatusBarTransitions); } @@ -1668,7 +1668,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { /** Temporarily hides Bubbles if the status bar is hidden. */ @Override public void updateBubblesVisibility() { - StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue(); + StatusBarMode mode = + mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode().getValue(); mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged( mode != StatusBarMode.LIGHTS_OUT && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT @@ -2927,45 +2928,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - /** - * Dismiss the keyguard then execute an action. - * - * @param action The action to execute after dismissing the keyguard. - * @param collapsePanel Whether we should collapse the panel after dismissing the keyguard. - * @param willAnimateOnKeyguard Whether {@param action} will run an animation on the keyguard if - * we are locked. - */ - private void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone, - boolean collapsePanel, boolean willAnimateOnKeyguard) { - if (!mDeviceProvisionedController.isDeviceProvisioned()) return; - - OnDismissAction onDismissAction = new OnDismissAction() { - @Override - public boolean onDismiss() { - new Thread(() -> { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - action.run(); - }).start(); - - return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard; - } - - @Override - public boolean willRunAnimationOnKeyguard() { - return willAnimateOnKeyguard; - } - }; - mActivityStarter.dismissKeyguardThenExecute(onDismissAction, /* cancel= */ null, - afterKeyguardGone); - } - private void clearNotificationEffects() { try { mBarService.clearNotificationEffects(); @@ -2993,7 +2955,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // End Extra BaseStatusBarMethods. boolean isTransientShown() { - return mStatusBarModeRepository.isTransientShown().getValue(); + return mStatusBarModeRepository.getDefaultDisplay().isTransientShown().getValue(); } private void updateLightRevealScrimVisibility() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index b0183d3fbd40..674f1698d2a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -98,7 +98,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr FACE_UNLOCK_BYPASS_NEVER -> false else -> field } - return enabled && mKeyguardStateController.isFaceEnrolled && + return enabled && mKeyguardStateController.isFaceEnrolledAndEnabled && isPostureAllowedForFaceAuth() } private set(value) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index 332984413dde..32b3ac2ad150 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -22,7 +22,6 @@ import android.hardware.Sensor import android.hardware.TriggerEvent import android.hardware.TriggerEventListener import com.android.keyguard.ActiveUnlockConfig -import com.android.keyguard.FaceAuthApiRequestReason import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.CoreStartable @@ -77,9 +76,6 @@ class KeyguardLiftController @Inject constructor( isListening = false updateListeningState() keyguardFaceAuthInteractor.onDeviceLifted() - keyguardUpdateMonitor.requestFaceAuth( - FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED - ) keyguardUpdateMonitor.requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE, "KeyguardLiftController") @@ -117,7 +113,8 @@ class KeyguardLiftController @Inject constructor( val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible && !statusBarStateController.isDozing - val shouldListen = (onKeyguard || bouncerVisible) && keyguardUpdateMonitor.isFaceEnrolled + val isFaceEnabled = keyguardFaceAuthInteractor.isFaceAuthEnabledAndEnrolled() + val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled if (shouldListen != isListening) { isListening = shouldListen diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java index eba7fe09a8af..7c871e183740 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java @@ -36,6 +36,7 @@ import com.android.internal.statusbar.LetterboxDetails; import com.android.internal.view.AppearanceRegion; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope; import com.android.systemui.util.ViewController; @@ -51,7 +52,7 @@ import javax.inject.Named; * whether there are notifications when the device is in {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}. */ @StatusBarFragmentScope -public class LightsOutNotifController extends ViewController<View> { +public class LegacyLightsOutNotifController extends ViewController<View> { private final CommandQueue mCommandQueue; private final NotifLiveDataStore mNotifDataStore; private final WindowManager mWindowManager; @@ -63,7 +64,7 @@ public class LightsOutNotifController extends ViewController<View> { private int mDisplayId; @Inject - LightsOutNotifController( + LegacyLightsOutNotifController( @Named(LIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView, WindowManager windowManager, NotifLiveDataStore notifDataStore, @@ -72,7 +73,12 @@ public class LightsOutNotifController extends ViewController<View> { mWindowManager = windowManager; mNotifDataStore = notifDataStore; mCommandQueue = commandQueue; + } + @Override + protected void onInit() { + super.onInit(); + NotificationsLiveDataStoreRefactor.assertInLegacyMode(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index 4d3e2ad4813a..eec617bf91d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -42,7 +42,7 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.statusbar.data.model.StatusBarAppearance; -import com.android.systemui.statusbar.data.repository.StatusBarModeRepository; +import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.Compile; import com.android.systemui.util.kotlin.JavaAdapter; @@ -68,7 +68,7 @@ public class LightBarController implements private final JavaAdapter mJavaAdapter; private final SysuiDarkIconDispatcher mStatusBarIconController; private final BatteryController mBatteryController; - private final StatusBarModeRepository mStatusBarModeRepository; + private final StatusBarModeRepositoryStore mStatusBarModeRepository; private BiometricUnlockController mBiometricUnlockController; private LightBarTransitionsController mNavigationBarController; @@ -126,7 +126,7 @@ public class LightBarController implements DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, - StatusBarModeRepository statusBarModeRepository, + StatusBarModeRepositoryStore statusBarModeRepository, DumpManager dumpManager, DisplayTracker displayTracker) { mJavaAdapter = javaAdapter; @@ -146,7 +146,7 @@ public class LightBarController implements @Override public void start() { mJavaAdapter.alwaysCollectFlow( - mStatusBarModeRepository.getStatusBarAppearance(), + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(), this::onStatusBarAppearanceChanged); } @@ -476,7 +476,7 @@ public class LightBarController implements private final DarkIconDispatcher mDarkIconDispatcher; private final BatteryController mBatteryController; private final NavigationModeController mNavModeController; - private final StatusBarModeRepository mStatusBarModeRepository; + private final StatusBarModeRepositoryStore mStatusBarModeRepository; private final DumpManager mDumpManager; private final DisplayTracker mDisplayTracker; @@ -486,7 +486,7 @@ public class LightBarController implements DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, - StatusBarModeRepository statusBarModeRepository, + StatusBarModeRepositoryStore statusBarModeRepository, DumpManager dumpManager, DisplayTracker displayTracker) { mJavaAdapter = javaAdapter; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 274b50fd79fd..daadedb06187 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -1636,13 +1636,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } /** - * Request to authenticate using face. - */ - public void requestFace(boolean request) { - mKeyguardUpdateManager.requestFaceAuthOnOccludingApp(request); - } - - /** * Request to authenticate using the fingerprint sensor. If the fingerprint sensor is udfps, * uses the color provided by udfpsColor for the fingerprint icon. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt new file mode 100644 index 000000000000..ed8b3e8922f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.phone.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.data.model.StatusBarMode +import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** + * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where status + * bar and navigation icons dim. In this mode, a notification dot appears where the notification + * icons would appear if they would be shown outside of this mode. + * + * This interactor knows whether the device is in [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE]. + */ +@SysUISingleton +class LightsOutInteractor +@Inject +constructor(private val repository: StatusBarModeRepositoryStore) { + + fun isLowProfile(displayId: Int): Flow<Boolean> = + repository.forDisplay(displayId).statusBarMode.map { + when (it) { + StatusBarMode.LIGHTS_OUT, + StatusBarMode.LIGHTS_OUT_TRANSPARENT -> true + else -> false + } + } +} 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 7adc08ca00c0..49880d4475da 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 @@ -43,7 +43,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; import com.android.systemui.shade.ShadeExpansionStateManager; @@ -139,7 +138,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final OngoingCallController mOngoingCallController; private final SystemStatusAnimationScheduler mAnimationScheduler; private final StatusBarLocationPublisher mLocationPublisher; - private final FeatureFlagsClassic mFeatureFlags; private final NotificationIconAreaController mNotificationIconAreaController; private final ShadeExpansionStateManager mShadeExpansionStateManager; private final StatusBarIconController mStatusBarIconController; @@ -228,7 +226,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue StatusBarLocationPublisher locationPublisher, NotificationIconAreaController notificationIconAreaController, ShadeExpansionStateManager shadeExpansionStateManager, - FeatureFlagsClassic featureFlags, StatusBarIconController statusBarIconController, DarkIconManager.Factory darkIconManagerFactory, CollapsedStatusBarViewModel collapsedStatusBarViewModel, @@ -258,7 +255,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mLocationPublisher = locationPublisher; mNotificationIconAreaController = notificationIconAreaController; mShadeExpansionStateManager = shadeExpansionStateManager; - mFeatureFlags = featureFlags; mStatusBarIconController = statusBarIconController; mCollapsedStatusBarViewModel = collapsedStatusBarViewModel; mCollapsedStatusBarViewBinder = collapsedStatusBarViewBinder; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java index 0618abbf00d8..96faa359d43e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java @@ -18,8 +18,9 @@ package com.android.systemui.statusbar.phone.fragment.dagger; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.RootView; +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.phone.LightsOutNotifController; +import com.android.systemui.statusbar.phone.LegacyLightsOutNotifController; import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; @@ -78,7 +79,9 @@ public interface StatusBarFragmentComponent { getBatteryMeterViewController().init(); getHeadsUpAppearanceController().init(); getPhoneStatusBarViewController().init(); - getLightsOutNotifController().init(); + if (!NotificationsLiveDataStoreRefactor.isEnabled()) { + getLegacyLightsOutNotifController().init(); + } getStatusBarDemoMode().init(); } @@ -101,7 +104,7 @@ public interface StatusBarFragmentComponent { /** */ @StatusBarFragmentScope - LightsOutNotifController getLightsOutNotifController(); + LegacyLightsOutNotifController getLegacyLightsOutNotifController(); /** */ @StatusBarFragmentScope diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index b0532ce1817b..0bdd1a5b4d5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -36,7 +36,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.statusbar.data.repository.StatusBarModeRepository +import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection @@ -68,7 +68,7 @@ class OngoingCallController @Inject constructor( private val dumpManager: DumpManager, private val statusBarWindowController: StatusBarWindowController, private val swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler, - private val statusBarModeRepository: StatusBarModeRepository, + private val statusBarModeRepository: StatusBarModeRepositoryStore, ) : CallbackController<OngoingCallListener>, Dumpable, CoreStartable { private var isFullscreen: Boolean = false /** Non-null if there's an active call notification. */ @@ -129,7 +129,7 @@ class OngoingCallController @Inject constructor( dumpManager.registerDumpable(this) notifCollection.addCollectionListener(notifListener) scope.launch { - statusBarModeRepository.isInFullscreenMode.collect { + statusBarModeRepository.defaultDisplay.isInFullscreenMode.collect { isFullscreen = it updateChipClickListener() updateGestureListening() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt index da9c45ad5ada..9c78ab42a14a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt @@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.asStateFlow * * This class is used to break a dependency cycle between * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController] and - * [com.android.systemui.statusbar.data.repository.StatusBarModeRepository]. Instead, those two + * [com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore]. Instead, those two * classes both refer to this repository. */ @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt index b9b88f4b762c..7d7f49bb8d17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -16,11 +16,15 @@ package com.android.systemui.statusbar.pipeline.shared.ui.binder +import android.animation.Animator +import android.animation.AnimatorListenerAdapter import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel import javax.inject.Inject import kotlinx.coroutines.launch @@ -61,9 +65,49 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa listener.onTransitionFromLockscreenToDreamStarted() } } + + if (NotificationsLiveDataStoreRefactor.isEnabled) { + val displayId = view.display.displayId + val lightsOutView: View = view.requireViewById(R.id.notification_lights_out) + launch { + viewModel.areNotificationsLightsOut(displayId).collect { show -> + animateLightsOutView(lightsOutView, show) + } + } + } } } } + + private fun animateLightsOutView(view: View, visible: Boolean) { + view.animate().cancel() + + val alpha = if (visible) 1f else 0f + val duration = if (visible) 750L else 250L + val visibility = if (visible) View.VISIBLE else View.GONE + + if (visible) { + view.alpha = 0f + view.visibility = View.VISIBLE + } + + view + .animate() + .alpha(alpha) + .setDuration(duration) + .setListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + view.alpha = alpha + view.visibility = visibility + // Unset the listener, otherwise this may persist for + // another view property animation + view.animate().setListener(null) + } + } + ) + .start() + } } /** Listener for various events that may affect the status bar's visibility. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt index 15ab143a7aeb..52a6d8cf0952 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt @@ -20,11 +20,17 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor +import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -48,12 +54,25 @@ interface CollapsedStatusBarViewModel { /** Emits whenever a transition from lockscreen to dream has started. */ val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> + + /** + * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where + * status bar and navigation icons dim. In this mode, a notification dot appears where the + * notification icons would appear if they would be shown outside of this mode. + * + * This flow tells when to show or hide the notification dot in the status bar to indicate + * whether there are notifications when the device is in + * [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE]. + */ + fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> } @SysUISingleton class CollapsedStatusBarViewModelImpl @Inject constructor( + private val lightsOutInteractor: LightsOutInteractor, + private val notificationsInteractor: ActiveNotificationsInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, @Application coroutineScope: CoroutineScope, ) : CollapsedStatusBarViewModel { @@ -69,4 +88,17 @@ constructor( keyguardTransitionInteractor.lockscreenToDreamingTransition .filter { it.transitionState == TransitionState.STARTED } .map {} + + override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = + if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) { + emptyFlow() + } else { + combine( + notificationsInteractor.areAnyNotificationsPresent, + lightsOutInteractor.isLowProfile(displayId), + ) { hasNotifications, isLowProfile -> + hasNotifications && isLowProfile + } + .distinctUntilChanged() + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index 52133ee5b7cd..ad2b070788f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -130,7 +130,7 @@ public interface KeyguardStateController extends CallbackController<Callback> { /** * If there are faces enrolled and user enabled face auth on keyguard. */ - default boolean isFaceEnrolled() { + default boolean isFaceEnrolledAndEnabled() { return false; } @@ -265,7 +265,7 @@ public interface KeyguardStateController extends CallbackController<Callback> { /** * Triggered when face auth becomes available or unavailable. Value should be queried with - * {@link KeyguardStateController#isFaceEnrolled()}. + * {@link KeyguardStateController#isFaceEnrolledAndEnabled()}. */ default void onFaceEnrolledChanged() {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 8cc7e7d21fc2..3deb9e7414af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -85,7 +85,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum private boolean mTrustManaged; private boolean mTrusted; private boolean mDebugUnlocked = false; - private boolean mFaceEnrolled; + private boolean mFaceEnrolledAndEnabled; private float mDismissAmount = 0f; private boolean mDismissingFromTouch = false; @@ -260,16 +260,16 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum || (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB && mDebugUnlocked); boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user); boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user); - boolean faceEnrolled = mKeyguardUpdateMonitor.isFaceEnrolled(user); + boolean faceEnabledAndEnrolled = mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled(); boolean changed = secure != mSecure || canDismissLockScreen != mCanDismissLockScreen || trustManaged != mTrustManaged || mTrusted != trusted - || mFaceEnrolled != faceEnrolled; + || mFaceEnrolledAndEnabled != faceEnabledAndEnrolled; if (changed || updateAlways) { mSecure = secure; mCanDismissLockScreen = canDismissLockScreen; mTrusted = trusted; mTrustManaged = trustManaged; - mFaceEnrolled = faceEnrolled; + mFaceEnrolledAndEnabled = faceEnabledAndEnrolled; mLogger.logKeyguardStateUpdate( mSecure, mCanDismissLockScreen, mTrusted, mTrustManaged); notifyUnlockedChanged(); @@ -290,8 +290,8 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override - public boolean isFaceEnrolled() { - return mFaceEnrolled; + public boolean isFaceEnrolledAndEnabled() { + return mFaceEnrolledAndEnabled; } @Override @@ -416,7 +416,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum pw.println(" mTrustManaged: " + mTrustManaged); pw.println(" mTrusted: " + mTrusted); pw.println(" mDebugUnlocked: " + mDebugUnlocked); - pw.println(" mFaceEnrolled: " + mFaceEnrolled); + pw.println(" mFaceEnrolled: " + mFaceEnrolledAndEnabled); pw.println(" isKeyguardFadingAway: " + isKeyguardFadingAway()); pw.println(" isKeyguardGoingAway: " + isKeyguardGoingAway()); pw.println(" isLaunchTransitionFadingAway: " + isLaunchTransitionFadingAway()); diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 5fc435aae67f..cf76c0d2e696 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -29,7 +29,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.model.SelectedUserModel @@ -120,7 +119,6 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, private val globalSettings: GlobalSettings, private val tracker: UserTracker, - featureFlags: FeatureFlags, ) : UserRepository { private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> = diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index d65a69c62072..9ee3d220a79b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -64,13 +64,13 @@ import androidx.lifecycle.Observer; import com.android.internal.annotations.GuardedBy; import com.android.settingslib.volume.MediaSessions; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.qs.tiles.DndTile; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.RingerModeLiveData; @@ -99,6 +99,7 @@ import javax.inject.Inject; @SysUISingleton public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable { private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class); + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000; private static final int DYNAMIC_STREAM_START_INDEX = 100; @@ -1339,14 +1340,24 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private boolean showForSession(Token token) { if (mVolumeAdjustmentForRemoteGroupSessions) { + if (DEBUG) { + Log.d(TAG, "Volume adjustment for remote group sessions allowed," + + " showForSession: true"); + } return true; } MediaController ctr = new MediaController(mContext, token); String packageName = ctr.getPackageName(); List<RoutingSessionInfo> sessions = mRouter2Manager.getRoutingSessions(packageName); - + if (DEBUG) { + Log.d(TAG, "Found " + sessions.size() + " routing sessions for package name " + + packageName); + } for (RoutingSessionInfo session : sessions) { + if (DEBUG) { + Log.d(TAG, "Found routingSessionInfo: " + session); + } if (!session.isSystemSession() && session.getVolumeHandling() != MediaRoute2Info.PLAYBACK_VOLUME_FIXED) { return true; diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java index 2132904caa84..5558aa72bb57 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java @@ -214,14 +214,12 @@ public class WalletActivity extends ComponentActivity implements Utils.getColorAttrDefaultColor( this, com.android.internal.R.attr.colorAccentPrimary)); mKeyguardFaceAuthInteractor.onWalletLaunched(); - mKeyguardViewManager.requestFace(true); } @Override protected void onPause() { super.onPause(); mKeyguardViewManager.requestFp(false, -1); - mKeyguardViewManager.requestFace(false); } @Override diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt index e429446f66cf..3f76d303898e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt @@ -261,7 +261,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // GIVEN fingerprint and face are NOT enrolled activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor - `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false) + `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(false) `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false) // WHEN unlock intent is allowed when NO biometrics are enrolled (0) @@ -291,7 +291,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // GIVEN fingerprint and face are both enrolled activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor - `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) + `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true) `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true) // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs @@ -314,7 +314,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ) // WHEN fingerprint ONLY enrolled - `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false) + `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(false) `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(true) // THEN active unlock triggers allowed on unlock intent @@ -325,7 +325,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ) // WHEN face ONLY enrolled - `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) + `when`(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true) `when`(keyguardUpdateMonitor.isUnlockWithFingerprintPossible(0)).thenReturn(false) // THEN active unlock triggers allowed on unlock intent diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 2e9b7e84344b..b403d1d5fbdc 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -125,10 +125,7 @@ class ClockEventControllerTest : SysuiTestCase() { repository = repository, ) - withDeps.featureFlags.apply { - set(Flags.REGION_SAMPLING, false) - set(Flags.FACE_AUTH_REFACTOR, false) - } + withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) } underTest = ClockEventController( withDeps.keyguardInteractor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index 7feab9141da2..adf0adabe24c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -18,7 +18,6 @@ package com.android.keyguard; 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; @@ -179,7 +178,6 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView); mExecutor = new FakeExecutor(new FakeSystemClock()); mFakeFeatureFlags = new FakeFeatureFlags(); - mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false); mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); mFakeFeatureFlags.set(MIGRATE_CLOCKS_TO_BLUEPRINT, false); mController = new KeyguardClockSwitchController( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 225f12536a33..543b2910bbda 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -47,6 +47,7 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.log.SessionTracker @@ -137,6 +138,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback @Mock private lateinit var audioManager: AudioManager @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var postureController: DevicePostureController @@ -257,7 +259,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { telephonyManager, viewMediatorCallback, audioManager, - mock(), + faceAuthInteractor, mock(), { JavaAdapter(sceneTestUtils.testScope.backgroundScope) }, mSelectedUserInteractor, @@ -576,49 +578,12 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test - fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() { + fun onSwipeUp_forwardsItToFaceAuthInteractor() { val registeredSwipeListener = registeredSwipeListener - whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false) setupGetSecurityView(SecurityMode.Password) registeredSwipeListener.onSwipeUp() - verify(keyguardUpdateMonitor).requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) - } - - @Test - fun onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() { - val registeredSwipeListener = registeredSwipeListener - whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true) - registeredSwipeListener.onSwipeUp() - verify(keyguardUpdateMonitor, never()) - .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) - } - @Test - fun onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() { - val registeredSwipeListener = registeredSwipeListener - whenever( - keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) - ) - .thenReturn(true) - setupGetSecurityView(SecurityMode.Password) - clearInvocations(viewFlipperController) - registeredSwipeListener.onSwipeUp() - viewControllerImmediately - verify(keyguardPasswordViewControllerMock) - .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true) - } - - @Test - fun onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() { - val registeredSwipeListener = registeredSwipeListener - whenever( - keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) - ) - .thenReturn(false) - setupGetSecurityView(SecurityMode.Password) - registeredSwipeListener.onSwipeUp() - verify(keyguardPasswordViewControllerMock, never()) - .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true) + verify(faceAuthInteractor).onSwipeUpOnBouncer() } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 146715d26b7d..13fb42ce8c3e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; +import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; @@ -70,6 +71,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardClockSwitch mKeyguardClockSwitch; @Mock protected FrameLayout mMediaHostContainer; + @Mock protected KeyguardStatusAreaView mKeyguardStatusAreaView; @Before public void setup() { @@ -109,6 +111,8 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver); when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch); when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow()); + when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area)) + .thenReturn(mKeyguardStatusAreaView); } protected void givenViewAttached() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 948942fbce3a..9c3288b9f93d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static junit.framework.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; @@ -27,6 +29,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.animation.AnimatorTestRule; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -40,6 +43,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -51,6 +55,9 @@ import java.lang.reflect.Field; @RunWith(AndroidTestingRunner.class) public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest { + @Rule + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + @Test public void dozeTimeTick_updatesSlice() { mController.dozeTimeTick(); @@ -230,4 +237,34 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll throw new RuntimeException(e); } } + + @Test + public void statusAreaHeightChange_animatesHeightOutputChange() { + // Init & Capture Layout Listener + mController.onInit(); + mController.onViewAttached(); + + when(mDozeParameters.getAlwaysOn()).thenReturn(true); + ArgumentCaptor<View.OnLayoutChangeListener> captor = + ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); + verify(mKeyguardStatusAreaView).addOnLayoutChangeListener(captor.capture()); + View.OnLayoutChangeListener listener = captor.getValue(); + + // Setup and validate initial height + when(mKeyguardStatusView.getHeight()).thenReturn(200); + when(mKeyguardClockSwitchController.getNotificationIconAreaHeight()).thenReturn(10); + assertEquals(190, mController.getLockscreenHeight()); + + // Trigger Change and validate value unchanged immediately + when(mKeyguardStatusAreaView.getHeight()).thenReturn(100); + when(mKeyguardStatusView.getHeight()).thenReturn(300); // Include child height + listener.onLayoutChange(mKeyguardStatusAreaView, + /* new layout */ 100, 300, 200, 400, + /* old layout */ 100, 300, 200, 300); + assertEquals(190, mController.getLockscreenHeight()); + + // Complete animation, validate height increased + mAnimatorTestRule.advanceTimeBy(200); + assertEquals(290, mController.getLockscreenHeight()); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 2b41e08065d1..1ab634c46de9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -23,24 +23,15 @@ import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPR import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; import static android.hardware.biometrics.SensorProperties.STRENGTH_CONVENIENCE; import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; -import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN; -import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; -import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED; -import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; -import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; -import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; -import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; -import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; @@ -88,12 +79,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; -import android.hardware.display.DisplayManagerGlobal; -import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceManager; -import android.hardware.face.FaceSensorProperties; -import android.hardware.face.FaceSensorPropertiesInternal; -import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -121,11 +107,6 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.text.TextUtils; -import android.view.Display; -import android.view.DisplayAdjustments; -import android.view.DisplayInfo; - -import androidx.annotation.NonNull; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.jank.InteractionJankMonitor; @@ -143,10 +124,13 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener; +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; +import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.FaceDetectionStatus; +import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -199,7 +183,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "", DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID, TEST_CARRIER_ID, 0); - private static final int FACE_SENSOR_ID = 0; private static final int FINGERPRINT_SENSOR_ID = 1; @Mock @@ -217,8 +200,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private FingerprintManager mFingerprintManager; @Mock - private FaceManager mFaceManager; - @Mock private BiometricManager mBiometricManager; @Mock private PackageManager mPackageManager; @@ -277,19 +258,18 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private IActivityTaskManager mActivityTaskManager; @Mock - private WakefulnessLifecycle mWakefulness; - @Mock private SelectedUserInteractor mSelectedUserInteractor; + @Mock + private KeyguardFaceAuthInteractor mFaceAuthInteractor; + @Captor + private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener; - private List<FaceSensorPropertiesInternal> mFaceSensorProperties; private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties; private final int mCurrentUserId = 100; @Captor private ArgumentCaptor<IBiometricEnabledOnKeyguardCallback> mBiometricEnabledCallbackArgCaptor; - @Captor - private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor; // Direct executor private final Executor mBackgroundExecutor = Runnable::run; @@ -303,14 +283,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig; private IFingerprintAuthenticatorsRegisteredCallback mFingerprintAuthenticatorsRegisteredCallback; - private IFaceAuthenticatorsRegisteredCallback mFaceAuthenticatorsRegisteredCallback; private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999); - private FakeDisplayTracker mDisplayTracker; @Before public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); - mDisplayTracker = new FakeDisplayTracker(mContext); when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId); when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true); @@ -358,12 +335,14 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); setupBiometrics(mKeyguardUpdateMonitor); + mKeyguardUpdateMonitor.setFaceAuthInteractor(mFaceAuthInteractor); + verify(mFaceAuthInteractor).registerListener(mFaceAuthenticationListener.capture()); } private void setupBiometrics(KeyguardUpdateMonitor keyguardUpdateMonitor) throws RemoteException { captureAuthenticatorsRegisteredCallbacks(); - setupFaceAuth(/* isClass3 */ false); + when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(false); setupFingerprintAuth(/* isClass3 */ true); verify(mBiometricManager) @@ -387,12 +366,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void captureAuthenticatorsRegisteredCallbacks() throws RemoteException { - ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor = - ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class); - verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture()); - mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue(); - mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); - ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor = ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( @@ -402,16 +375,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { .onAllAuthenticatorsRegistered(mFingerprintSensorProperties); } - private void setupFaceAuth(boolean isClass3) throws RemoteException { - when(mFaceManager.isHardwareDetected()).thenReturn(true); - when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true); - mFaceSensorProperties = - List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false, isClass3)); - when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties); - mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); - assertEquals(isClass3, mKeyguardUpdateMonitor.isFaceClass3()); - } - private void setupFingerprintAuth(boolean isClass3) throws RemoteException { when(mAuthController.isFingerprintEnrolled(anyInt())).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); @@ -442,28 +405,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { true /* resetLockoutRequiresHardwareAuthToken */); } - @NonNull - private FaceSensorPropertiesInternal createFaceSensorProperties( - boolean supportsFaceDetection, boolean isClass3) { - final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); - componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, - "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, - "00000001" /* serialNumber */, "" /* softwareVersion */)); - componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */, - "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, - "vendor/version/revision" /* softwareVersion */)); - - return new FaceSensorPropertiesInternal( - FACE_SENSOR_ID /* id */, - isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE, - 1 /* maxTemplatesAllowed */, - componentInfo, - FaceSensorProperties.TYPE_UNKNOWN, - supportsFaceDetection /* supportsFaceDetection */, - true /* supportsSelfIllumination */, - false /* resetLockoutRequiresChallenge */); - } - @After public void tearDown() { if (mMockitoSession != null) { @@ -887,13 +828,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void whenDetectFace_biometricDetectCallback() throws RemoteException { - ArgumentCaptor<FaceManager.FaceDetectionCallback> faceDetectCallbackCaptor = - ArgumentCaptor.forClass(FaceManager.FaceDetectionCallback.class); - - givenDetectFace(); - verify(mFaceManager).detectFace(any(), faceDetectCallbackCaptor.capture(), any()); - faceDetectCallbackCaptor.getValue().onFaceDetected(0, 0, false); + public void whenDetectFace_biometricDetectCallback() { + mFaceAuthenticationListener.getValue().onDetectionStatusChanged( + new FaceDetectionStatus(0, 0, false, 0L)); // THEN verify keyguardUpdateMonitorCallback receives a detect callback // and NO authenticate callbacks @@ -926,40 +863,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void class3FingerprintLockOut_lockOutClass1Face() throws RemoteException { - setupFaceAuth(/* isClass3 */ false); - setupFingerprintAuth(/* isClass3 */ true); - - // GIVEN primary auth is not required by StrongAuthTracker - primaryAuthNotRequiredByStrongAuthTracker(); - - // WHEN fingerprint (class 3) is lock out - fingerprintErrorTemporaryLockOut(); - - // THEN unlocking with face is not allowed - Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - BiometricSourceType.FACE)); - } - - @Test - public void class3FingerprintLockOut_lockOutClass3Face() throws RemoteException { - setupFaceAuth(/* isClass3 */ true); - setupFingerprintAuth(/* isClass3 */ true); - - // GIVEN primary auth is not required by StrongAuthTracker - primaryAuthNotRequiredByStrongAuthTracker(); - - // WHEN fingerprint (class 3) is lock out - fingerprintErrorTemporaryLockOut(); - - // THEN unlocking with face is not allowed - Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - BiometricSourceType.FACE)); - } - - @Test public void class3FaceLockOut_lockOutClass3Fingerprint() throws RemoteException { - setupFaceAuth(/* isClass3 */ true); + when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true); + when(mFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()).thenReturn(true); + setupFingerprintAuth(/* isClass3 */ true); // GIVEN primary auth is not required by StrongAuthTracker @@ -975,7 +882,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void class1FaceLockOut_doesNotLockOutClass3Fingerprint() throws RemoteException { - setupFaceAuth(/* isClass3 */ false); + when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(false); setupFingerprintAuth(/* isClass3 */ true); // GIVEN primary auth is not required by StrongAuthTracker @@ -1056,162 +963,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testTriesToAuthenticate_whenBouncer() { - setKeyguardBouncerVisibility(true); - verifyFaceAuthenticateCall(); - } - - @Test - public void testNoStartAuthenticate_whenAboutToShowBouncer() { - mKeyguardUpdateMonitor.sendPrimaryBouncerChanged( - /* bouncerIsOrWillBeShowing */ true, /* bouncerFullyShown */ false); - - verifyFaceAuthenticateNeverCalled(); - } - - @Test - public void testTriesToAuthenticate_whenKeyguard() { - keyguardIsVisible(); - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - - verifyFaceAuthenticateCall(); - verify(mUiEventLogger).logWithInstanceIdAndPosition( - eq(FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP), - eq(0), - eq(null), - any(), - eq(PowerManager.WAKE_REASON_POWER_BUTTON)); - } - - @Test - public void skipsAuthentication_whenStatusBarShadeLocked() { - mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED); - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - - keyguardIsVisible(); - verifyFaceAuthenticateNeverCalled(); - } - - @Test - public void skipsAuthentication_whenStrongAuthRequired_nonBypass() { - lockscreenBypassIsNotAllowed(); - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); - - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - keyguardIsVisible(); - - verifyFaceAuthenticateNeverCalled(); - } - - @Test - public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() - throws RemoteException { - // GIVEN bypass is enabled, face detection is supported - lockscreenBypassIsAllowed(); - supportsFaceDetection(); - keyguardIsVisible(); - - // GIVEN udfps is supported and strong auth required for weak biometrics (face) only - givenUdfpsSupported(); - primaryAuthRequiredForWeakBiometricOnly(); // allows class3 fp to run but not class1 face - - // WHEN the device wakes up - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - - // THEN face detect and authenticate are NOT triggered - verifyFaceDetectNeverCalled(); - verifyFaceAuthenticateNeverCalled(); - - // THEN biometric help message sent to callback - verify(mTestCallback).onBiometricHelp( - eq(BIOMETRIC_HELP_FACE_NOT_AVAILABLE), anyString(), eq(BiometricSourceType.FACE)); - } - - @Test - public void faceDetect_whenStrongAuthRequiredAndBypass() throws RemoteException { - givenDetectFace(); - - // FACE detect is triggered, not authenticate - verifyFaceDetectCall(); - verifyFaceAuthenticateNeverCalled(); - - // WHEN bouncer becomes visible - setKeyguardBouncerVisibility(true); - clearInvocations(mFaceManager); - - // THEN face scanning is not run - mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN); - verifyFaceAuthenticateNeverCalled(); - verifyFaceDetectNeverCalled(); - } - - @Test - public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() { - // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required - lockscreenBypassIsAllowed(); - primaryAuthRequiredEncrypted(); - keyguardIsVisible(); - - // WHEN the device wakes up - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - - // FACE detect and authenticate are NOT triggered - verifyFaceDetectNeverCalled(); - verifyFaceAuthenticateNeverCalled(); - } - - @Test - public void requestFaceAuth_whenFaceAuthWasStarted_returnsTrue() throws RemoteException { - // This satisfies all the preconditions to run face auth. - keyguardNotGoingAway(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - bouncerFullyVisibleAndNotGoingToSleep(); - mTestableLooper.processAllMessages(); - - boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth( - NOTIFICATION_PANEL_CLICKED); - - assertThat(didFaceAuthRun).isTrue(); - } - - @Test - public void requestFaceAuth_whenFaceAuthWasNotStarted_returnsFalse() throws RemoteException { - // This ensures face auth won't run. - biometricsDisabledForCurrentUser(); - mTestableLooper.processAllMessages(); - - boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth( - NOTIFICATION_PANEL_CLICKED); - - assertThat(didFaceAuthRun).isFalse(); - } - - @Test - public void testTriesToAuthenticate_whenAssistant() { - mKeyguardUpdateMonitor.setKeyguardShowing(true, true); - mKeyguardUpdateMonitor.setAssistantVisible(true); - - verifyFaceAuthenticateCall(); - } - - @Test - public void doesNotTryToAuthenticateWhenKeyguardIsNotShowingButOccluded_whenAssistant() { - mKeyguardUpdateMonitor.setKeyguardShowing(false, true); - mKeyguardUpdateMonitor.setAssistantVisible(true); - - verifyFaceAuthenticateNeverCalled(); - } - - @Test public void noFpListeningWhenKeyguardIsOccluded_unlessAlternateBouncerShowing() { // GIVEN device is awake but occluded mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); @@ -1257,62 +1008,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() { - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - lockscreenBypassIsAllowed(); - mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */, - mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, - new ArrayList<>()); - keyguardIsVisible(); - verifyFaceAuthenticateCall(); - } - - @Test - public void faceUnlockDoesNotRunWhenDeviceIsGoingToSleepWithAssistantVisible() { - mKeyguardUpdateMonitor.setKeyguardShowing(true, true); - mKeyguardUpdateMonitor.setAssistantVisible(true); - - verifyFaceAuthenticateCall(); - mTestableLooper.processAllMessages(); - clearInvocations(mFaceManager); - - // Device going to sleep while assistant is visible - mKeyguardUpdateMonitor.handleStartedGoingToSleep(0); - mKeyguardUpdateMonitor.handleFinishedGoingToSleep(0); - mTestableLooper.moveTimeForward(DEFAULT_CANCEL_SIGNAL_TIMEOUT); - mTestableLooper.processAllMessages(); - - mKeyguardUpdateMonitor.handleKeyguardReset(); - - assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse(); - verifyFaceAuthenticateNeverCalled(); - } - - @Test - public void testIgnoresAuth_whenTrustAgentOnKeyguard_withoutBypass() { - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */, - mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>()); - keyguardIsVisible(); - verifyFaceAuthenticateNeverCalled(); - } - - @Test - public void testNoFaceAuth_whenLockDown() { - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); - userDeviceLockDown(); - - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - keyguardIsVisible(); - mTestableLooper.processAllMessages(); - - verifyFaceAuthenticateNeverCalled(); - verifyFaceDetectNeverCalled(); - } - - @Test public void testFingerprintPowerPressed_restartsFingerprintListeningStateWithDelay() { mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback .onAuthenticationError(FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, ""); @@ -1329,18 +1024,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() { - // test whether face will be skipped if authenticated, so the value of isClass3Biometric - // doesn't matter here - mKeyguardUpdateMonitor.onFaceAuthenticated(mSelectedUserInteractor.getSelectedUserId(), - true /* isClass3Biometric */); - setKeyguardBouncerVisibility(true); - mTestableLooper.processAllMessages(); - - verifyFaceAuthenticateNeverCalled(); - } - - @Test public void testFaceAndFingerprintLockout_onlyFace() { mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); mTestableLooper.processAllMessages(); @@ -1379,7 +1062,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testGetUserCanSkipBouncer_whenFace() { int user = mSelectedUserInteractor.getSelectedUserId(); - mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */); + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(true /* isClass3Biometric */)) + .thenReturn(true); + when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true); + when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true); + assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); } @@ -1388,7 +1075,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */)) .thenReturn(false); int user = mSelectedUserInteractor.getSelectedUserId(); - mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */); + when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(false); + when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true); + assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse(); } @@ -1409,22 +1098,19 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testBiometricsCleared_whenUserSwitches() throws Exception { + public void testBiometricsCleared_whenUserSwitches() { final BiometricAuthenticated dummyAuthentication = new BiometricAuthenticated(true /* authenticated */, true /* strong */); - mKeyguardUpdateMonitor.mUserFaceAuthenticated.put(0 /* user */, dummyAuthentication); mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.put(0 /* user */, dummyAuthentication); assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(1); - assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(1); mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> { }); assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(0); - assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(0); } @Test - public void testMultiUserJankMonitor_whenUserSwitches() throws Exception { + public void testMultiUserJankMonitor_whenUserSwitches() { mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */); verify(mInteractionJankMonitor).end(InteractionJankMonitor.CUJ_USER_SWITCH); verify(mLatencyTracker).onActionEnd(LatencyTracker.ACTION_USER_SWITCH); @@ -1432,22 +1118,17 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testMultiUserLockoutChanged_whenUserSwitches() { - testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT, - BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT); + testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT); } @Test public void testMultiUserLockoutNotChanged_whenUserSwitches() { - testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_NONE, - BiometricConstants.BIOMETRIC_LOCKOUT_NONE); + testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_NONE); } private void testMultiUserLockout_whenUserSwitches( - @BiometricConstants.LockoutMode int fingerprintLockoutMode, - @BiometricConstants.LockoutMode int faceLockoutMode) { + @BiometricConstants.LockoutMode int fingerprintLockoutMode) { final int newUser = 12; - final boolean faceLockOut = - faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE; final boolean fpLockOut = fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE; @@ -1455,16 +1136,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); keyguardIsVisible(); - verifyFaceAuthenticateCall(); verifyFingerprintAuthenticateCall(); when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser))) .thenReturn(fingerprintLockoutMode); - when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser))) - .thenReturn(faceLockoutMode); - final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal); - mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel; KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); mKeyguardUpdateMonitor.registerCallback(callback); @@ -1472,17 +1148,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.handleUserSwitchComplete(newUser); mTestableLooper.processAllMessages(); - // THEN face and fingerprint listening are always cancelled immediately - verify(faceCancel).cancel(); - verify(callback).onBiometricRunningStateChanged( - eq(false), eq(BiometricSourceType.FACE)); + // THEN fingerprint listening are always cancelled immediately verify(fpCancel).cancel(); verify(callback).onBiometricRunningStateChanged( eq(false), eq(BiometricSourceType.FINGERPRINT)); // THEN locked out states are updated assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLockOut); - assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLockOut); // Fingerprint should be cancelled on lockout if going to lockout state, else // restarted if it's not @@ -1752,11 +1424,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void testShouldNotListenForUdfps_whenFaceAuthenticated() { // GIVEN a "we should listen for udfps" state mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); + when(mFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()).thenReturn(true); when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true); // WHEN face authenticated - mKeyguardUpdateMonitor.onFaceAuthenticated( - mSelectedUserInteractor.getSelectedUserId(), false); + when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true); // THEN we shouldn't listen for udfps assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false); @@ -1777,51 +1449,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testShouldNotUpdateBiometricListeningStateOnStatusBarStateChange() { - // GIVEN state for face auth should run aside from StatusBarState - biometricsNotDisabledThroughDevicePolicyManager(); - mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED); - setKeyguardBouncerVisibility(false /* isVisible */); - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - lockscreenBypassIsAllowed(); - keyguardIsVisible(); - - // WHEN status bar state reports a change to the keyguard that would normally indicate to - // start running face auth - mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isEqualTo(true); - - // THEN face unlock is not running b/c status bar state changes don't cause biometric - // listening state to update - assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(false); - - // WHEN biometric listening state is updated when showing state changes from false => true - mKeyguardUpdateMonitor.setKeyguardShowing(false, false); - mKeyguardUpdateMonitor.setKeyguardShowing(true, false); - - // THEN face unlock is running - assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(true); - } - - @Test - public void testRequestFaceAuthFromOccludingApp_whenInvoked_startsFaceAuth() { - mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true); - - assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue(); - } - - @Test - public void testRequestFaceAuthFromOccludingApp_whenInvoked_stopsFaceAuth() { - mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true); - - assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue(); - - mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); - - assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse(); - } - - @Test public void testRequireUnlockForNfc_Broadcast() { KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); mKeyguardUpdateMonitor.registerCallback(callback); @@ -1833,13 +1460,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testFaceDoesNotAuth_afterPinAttempt() { - mTestableLooper.processAllMessages(); - mKeyguardUpdateMonitor.setCredentialAttempted(); - verifyFaceAuthenticateNeverCalled(); - } - - @Test public void testShowTrustGrantedMessage_onTrustGranted() { // WHEN trust is enabled (ie: via some trust agent) with a trustGranted string mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */, @@ -1855,366 +1475,17 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testShouldListenForFace_whenFaceManagerNotAvailable_returnsFalse() { - cleanupKeyguardUpdateMonitor(); - mFaceManager = null; - - mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - @Test - public void testShouldListenForFace_whenFpIsLockedOut_returnsFalse() throws RemoteException { - // Face auth should run when the following is true. - keyguardNotGoingAway(); - occludingAppRequestsFaceAuth(); - currentUserIsSystem(); - primaryAuthNotRequiredByStrongAuthTracker(); - biometricsEnabledForCurrentUser(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - userNotCurrentlySwitching(); - mTestableLooper.processAllMessages(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - // Fingerprint is locked out. - fingerprintErrorTemporaryLockOut(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - @Test - public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse() - throws RemoteException { - // Face auth should run when the following is true. - bouncerFullyVisibleAndNotGoingToSleep(); - keyguardNotGoingAway(); - currentUserIsSystem(); - primaryAuthNotRequiredByStrongAuthTracker(); - biometricsEnabledForCurrentUser(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - userNotCurrentlySwitching(); - - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - triggerSuccessfulFaceAuth(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - @Test - public void testShouldListenForFace_whenFpIsAlreadyAuthenticated_returnsFalse() - throws RemoteException { - // Face auth should run when the following is true. - bouncerFullyVisibleAndNotGoingToSleep(); - keyguardNotGoingAway(); - currentUserIsSystem(); - primaryAuthNotRequiredByStrongAuthTracker(); - biometricsEnabledForCurrentUser(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - userNotCurrentlySwitching(); - - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - successfulFingerprintAuth(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - @Test - public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException { - cleanupKeyguardUpdateMonitor(); - // This disables face auth - when(mUserManager.isSystemUser()).thenReturn(false); - mKeyguardUpdateMonitor = - new TestableKeyguardUpdateMonitor(mContext); - - // Face auth should run when the following is true. - keyguardNotGoingAway(); - bouncerFullyVisibleAndNotGoingToSleep(); - primaryAuthNotRequiredByStrongAuthTracker(); - biometricsEnabledForCurrentUser(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - userNotCurrentlySwitching(); - mTestableLooper.processAllMessages(); - - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - @Test - public void testShouldListenForFace_whenStrongAuthDoesNotAllowScanning_returnsFalse() - throws RemoteException { - // Face auth should run when the following is true. - keyguardNotGoingAway(); - bouncerFullyVisibleAndNotGoingToSleep(); - currentUserIsSystem(); - biometricsEnabledForCurrentUser(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - userNotCurrentlySwitching(); - - // This disables face auth - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - @Test - public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse() - throws RemoteException { - keyguardNotGoingAway(); - bouncerFullyVisibleAndNotGoingToSleep(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - // This disables face auth - biometricsDisabledForCurrentUser(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - @Test - public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse() - throws RemoteException { - // Face auth should run when the following is true. - keyguardNotGoingAway(); - bouncerFullyVisibleAndNotGoingToSleep(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - userCurrentlySwitching(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - @Test - public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse() - throws RemoteException { - keyguardNotGoingAway(); - bouncerFullyVisibleAndNotGoingToSleep(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - secureCameraLaunched(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - @Test - public void shouldListenForFace_secureCameraLaunchedButAlternateBouncerIsLaunched_returnsTrue() - throws RemoteException { - // Face auth should run when the following is true. - keyguardNotGoingAway(); - bouncerFullyVisibleAndNotGoingToSleep(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - mTestableLooper.processAllMessages(); - - secureCameraLaunched(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - - alternateBouncerVisible(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - } - - @Test - public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue() - throws RemoteException { - // Face auth should run when the following is true. - keyguardNotGoingAway(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - - bouncerFullyVisibleAndNotGoingToSleep(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - } - - @Test - public void testShouldListenForFace_whenAuthInterruptIsActive_returnsTrue() - throws RemoteException { - // Face auth should run when the following is true. - keyguardNotGoingAway(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - - triggerAuthInterrupt(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - } - - @Test - public void testShouldListenForFace_whenKeyguardIsAwake_returnsTrue() throws RemoteException { - // Preconditions for face auth to run - keyguardNotGoingAway(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - - statusBarShadeIsLocked(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - - deviceNotGoingToSleep(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - deviceIsInteractive(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - keyguardIsVisible(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - statusBarShadeIsNotLocked(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - } - - @Test - public void testShouldListenForFace_whenUdfpsFingerDown_returnsTrue() throws RemoteException { - // Preconditions for face auth to run - keyguardNotGoingAway(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - when(mAuthController.isUdfpsFingerDown()).thenReturn(false); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - - when(mAuthController.isUdfpsFingerDown()).thenReturn(true); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - } - - @Test - public void testShouldListenForFace_whenAlternateBouncerIsShowing_returnsTrue() - throws RemoteException { - // Preconditions for face auth to run - keyguardNotGoingAway(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - mTestableLooper.processAllMessages(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - - mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - } - - @Test - public void testShouldListenForFace_alternateBouncerShowingButDeviceGoingToSleep_returnsFalse() - throws RemoteException { - // Preconditions for face auth to run - keyguardNotGoingAway(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - deviceNotGoingToSleep(); - alternateBouncerVisible(); - mTestableLooper.processAllMessages(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - deviceGoingToSleep(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - private void alternateBouncerVisible() { - mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); - } - - @Test - public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue() - throws RemoteException { - // Preconditions for face auth to run - keyguardNotGoingAway(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); - mTestableLooper.processAllMessages(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - // Face is locked out. - faceAuthLockOut(); - mTestableLooper.processAllMessages(); - - // This is needed beccause we want to show face locked out error message whenever face auth - // is supposed to run. - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - } - - @Test public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() { mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); mTestableLooper.processAllMessages(); keyguardIsVisible(); - verifyFaceAuthenticateCall(); verifyFingerprintAuthenticateCall(); - mKeyguardUpdateMonitor.onFaceAuthenticated(0, false); + when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true); + when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(false); + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */)) + .thenReturn(false); // Make sure keyguard is going away after face auth attempt, and that it calls // updateBiometricStateListeningState. mKeyguardUpdateMonitor.setKeyguardShowing(false, false); @@ -2234,34 +1505,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testDreamingStopped_faceDoesNotRun() { - mKeyguardUpdateMonitor.dispatchDreamingStopped(); - mTestableLooper.processAllMessages(); - - verifyFaceAuthenticateNeverCalled(); - } - - @Test - public void testFaceWakeupTrigger_runFaceAuth_onlyOnConfiguredTriggers() { - // keyguard is visible - keyguardIsVisible(); - - // WHEN device wakes up from an application - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_APPLICATION); - mTestableLooper.processAllMessages(); - - // THEN face auth isn't triggered - verifyFaceAuthenticateNeverCalled(); - - // WHEN device wakes up from the power button - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - - // THEN face auth is triggered - verifyFaceAuthenticateCall(); - } - - @Test public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_deviceInteractive() { // GIVEN device is interactive deviceIsInteractive(); @@ -2422,18 +1665,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testStrongAuthChange_lockDown_stopsFpAndFaceListeningState() { - // GIVEN device is listening for face and fingerprint + public void testStrongAuthChange_lockDown_stopsFpListeningState() { + // GIVEN device is listening for fingerprint mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); mTestableLooper.processAllMessages(); keyguardIsVisible(); - verifyFaceAuthenticateCall(); verifyFingerprintAuthenticateCall(); - final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal); - mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel; KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); mKeyguardUpdateMonitor.registerCallback(callback); @@ -2445,88 +1685,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mSelectedUserInteractor.getSelectedUserId()); mTestableLooper.processAllMessages(); - // THEN face and fingerprint listening are cancelled - verify(faceCancel).cancel(); - verify(callback).onBiometricRunningStateChanged( - eq(false), eq(BiometricSourceType.FACE)); + // THEN fingerprint listening are cancelled verify(fpCancel).cancel(); verify(callback).onBiometricRunningStateChanged( eq(false), eq(BiometricSourceType.FINGERPRINT)); } @Test - public void testNonStrongBiometricAllowedChanged_stopsFaceListeningState() { - // GIVEN device is listening for face and fingerprint - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - keyguardIsVisible(); - - verifyFaceAuthenticateCall(); - - final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); - mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; - KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); - mKeyguardUpdateMonitor.registerCallback(callback); - - // WHEN non-strong biometric allowed changes - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); - mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged( - mSelectedUserInteractor.getSelectedUserId()); - mTestableLooper.processAllMessages(); - - // THEN face and fingerprint listening are cancelled - verify(faceCancel).cancel(); - verify(callback).onBiometricRunningStateChanged( - eq(false), eq(BiometricSourceType.FACE)); - } - - @Test - public void testPostureChangeToUnsupported_stopsFaceListeningState() { - // GIVEN device is listening for face - mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_CLOSED; - deviceInPostureStateClosed(); - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - keyguardIsVisible(); - - verifyFaceAuthenticateCall(); - - final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); - mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; - KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); - mKeyguardUpdateMonitor.registerCallback(callback); - - // WHEN device is opened - deviceInPostureStateOpened(); - mTestableLooper.processAllMessages(); - - // THEN face listening is stopped. - verify(faceCancel).cancel(); - verify(callback).onBiometricRunningStateChanged( - eq(false), eq(BiometricSourceType.FACE)); - } - - @Test - public void testShouldListenForFace_withLockedDown_returnsFalse() - throws RemoteException { - keyguardNotGoingAway(); - bouncerFullyVisibleAndNotGoingToSleep(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - supportsFaceDetection(); - mTestableLooper.processAllMessages(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - userDeviceLockDown(); - - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - } - - @Test public void assistantVisible_requestActiveUnlock() { // GIVEN active unlock requests from the assistant are allowed when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -2549,8 +1714,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void fingerprintFailure_requestActiveUnlock_dismissKeyguard() - throws RemoteException { + public void fingerprintFailure_requestActiveUnlock_dismissKeyguard() { // GIVEN shouldTriggerActiveUnlock bouncerFullyVisible(); when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( @@ -2571,8 +1735,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void faceNonBypassFailure_requestActiveUnlock_doesNotDismissKeyguard() - throws RemoteException { + public void faceNonBypassFailure_requestActiveUnlock_doesNotDismissKeyguard() { // GIVEN shouldTriggerActiveUnlock when(mAuthController.isUdfpsFingerDown()).thenReturn(false); keyguardIsVisible(); @@ -2588,7 +1751,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // WHEN face fails & bypass is not allowed lockscreenBypassIsNotAllowed(); - mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed(); + mFaceAuthenticationListener.getValue().onAuthenticationStatusChanged( + new FailedFaceAuthenticationStatus()); // THEN request unlock with NO keyguard dismissal verify(mTrustManager).reportUserRequestedUnlock( @@ -2597,10 +1761,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void faceBypassFailure_requestActiveUnlock_dismissKeyguard() - throws RemoteException { + public void faceBypassFailure_requestActiveUnlock_dismissKeyguard() { // GIVEN shouldTriggerActiveUnlock when(mAuthController.isUdfpsFingerDown()).thenReturn(false); + when(mFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()).thenReturn(true); keyguardIsVisible(); keyguardNotGoingAway(); statusBarShadeIsNotLocked(); @@ -2614,7 +1778,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // WHEN face fails & bypass is not allowed lockscreenBypassIsAllowed(); - mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed(); + mFaceAuthenticationListener.getValue().onAuthenticationStatusChanged( + new FailedFaceAuthenticationStatus()); // THEN request unlock with a keyguard dismissal verify(mTrustManager).reportUserRequestedUnlock( @@ -2623,10 +1788,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void faceNonBypassFailure_requestActiveUnlock_dismissKeyguard() - throws RemoteException { + public void faceNonBypassFailure_requestActiveUnlock_dismissKeyguard() { // GIVEN shouldTriggerActiveUnlock when(mAuthController.isUdfpsFingerDown()).thenReturn(false); + when(mFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()).thenReturn(true); lockscreenBypassIsNotAllowed(); when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( true); @@ -2638,7 +1803,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // WHEN face fails & on the bouncer bouncerFullyVisible(); - mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed(); + mFaceAuthenticationListener.getValue().onAuthenticationStatusChanged( + new FailedFaceAuthenticationStatus()); // THEN request unlock with a keyguard dismissal verify(mTrustManager).reportUserRequestedUnlock( @@ -2647,54 +1813,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testShouldListenForFace_withAuthSupportPostureConfig_returnsTrue() - throws RemoteException { - mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_CLOSED; - keyguardNotGoingAway(); - bouncerFullyVisibleAndNotGoingToSleep(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - supportsFaceDetection(); - - deviceInPostureStateOpened(); - mTestableLooper.processAllMessages(); - // Should not listen for face when posture state in DEVICE_POSTURE_OPENED - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); - - deviceInPostureStateClosed(); - mTestableLooper.processAllMessages(); - // Should listen for face when posture state in DEVICE_POSTURE_CLOSED - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - } - - @Test - public void testShouldListenForFace_withoutAuthSupportPostureConfig_returnsTrue() - throws RemoteException { - mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_UNKNOWN; - keyguardNotGoingAway(); - bouncerFullyVisibleAndNotGoingToSleep(); - currentUserIsSystem(); - currentUserDoesNotHaveTrust(); - biometricsNotDisabledThroughDevicePolicyManager(); - biometricsEnabledForCurrentUser(); - userNotCurrentlySwitching(); - supportsFaceDetection(); - - deviceInPostureStateClosed(); - mTestableLooper.processAllMessages(); - // Whether device in any posture state, always listen for face - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - - deviceInPostureStateOpened(); - mTestableLooper.processAllMessages(); - // Whether device in any posture state, always listen for face - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); - } - - @Test public void testBatteryChangedIntent_refreshBatteryInfo() { mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(mContext, getBatteryIntent()); @@ -2727,8 +1845,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void unfoldWakeup_requestActiveUnlock_forceDismissKeyguard() - throws RemoteException { + public void unfoldWakeup_requestActiveUnlock_forceDismissKeyguard() { // GIVEN shouldTriggerActiveUnlock keyguardIsVisible(); when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( @@ -2754,8 +1871,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void unfoldWakeup_requestActiveUnlock_noDismissKeyguard() - throws RemoteException { + public void unfoldWakeup_requestActiveUnlock_noDismissKeyguard() { // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE keyguardIsVisible(); when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( @@ -2781,8 +1897,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void unfoldFromPostureChange_requestActiveUnlock_forceDismissKeyguard() - throws RemoteException { + public void unfoldFromPostureChange_requestActiveUnlock_forceDismissKeyguard() { // GIVEN shouldTriggerActiveUnlock keyguardIsVisible(); when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( @@ -2809,8 +1924,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test - public void unfoldFromPostureChange_requestActiveUnlock_noDismissKeyguard() - throws RemoteException { + public void unfoldFromPostureChange_requestActiveUnlock_noDismissKeyguard() { // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE keyguardIsVisible(); when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn( @@ -2859,44 +1973,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void faceAuthenticateOptions_bouncerAuthenticateReason() { - // GIVEN the bouncer is fully visible - bouncerFullyVisible(); - - // WHEN authenticate is called - ArgumentCaptor<FaceAuthenticateOptions> captor = - ArgumentCaptor.forClass(FaceAuthenticateOptions.class); - verify(mFaceManager).authenticate(any(), any(), any(), any(), captor.capture()); - - // THEN the authenticate reason is attributed to the bouncer - assertThat(captor.getValue().getAuthenticateReason()) - .isEqualTo(AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN); - } - - @Test - public void faceAuthenticateOptions_wakingUpAuthenticateReason_powerButtonWakeReason() { - // GIVEN keyguard is visible - keyguardIsVisible(); - - // WHEN device wakes up from the power button - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - - // THEN face auth is triggered - ArgumentCaptor<FaceAuthenticateOptions> captor = - ArgumentCaptor.forClass(FaceAuthenticateOptions.class); - verify(mFaceManager).authenticate(any(), any(), any(), any(), captor.capture()); - - // THEN the authenticate reason is attributed to the waking - assertThat(captor.getValue().getAuthenticateReason()) - .isEqualTo(AUTHENTICATE_REASON_STARTED_WAKING_UP); - - // THEN the wake reason is attributed to the power button - assertThat(captor.getValue().getWakeReason()) - .isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON); - } - - @Test public void testFingerprintSensorProperties() throws RemoteException { mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered( new ArrayList<>()); @@ -2913,26 +1989,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testFaceSensorProperties() throws RemoteException { - // GIVEN no face sensor properties - when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true); - mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(new ArrayList<>()); - - // THEN face is not possible - assertThat(mKeyguardUpdateMonitor.isUnlockWithFacePossible( - mSelectedUserInteractor.getSelectedUserId())).isFalse(); - - // WHEN there are face sensor properties - mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); - - // THEN face is possible but face does NOT start listening immediately - assertThat(mKeyguardUpdateMonitor.isUnlockWithFacePossible( - mSelectedUserInteractor.getSelectedUserId())).isTrue(); - verifyFaceAuthenticateNeverCalled(); - verifyFaceDetectNeverCalled(); - } - - @Test public void testFingerprintListeningStateWhenOccluded() { when(mAuthController.isUdfpsSupported()).thenReturn(true); @@ -3014,123 +2070,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { authCallback.getValue().onEnrollmentsChanged(BiometricAuthenticator.TYPE_FACE); mTestableLooper.processAllMessages(); verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE); - } - - @Test - public void onDisplayOn_nothingHappens() throws RemoteException { - // GIVEN - keyguardIsVisible(); - enableStopFaceAuthOnDisplayOff(); - - // WHEN the default display state changes to ON - triggerDefaultDisplayStateChangeToOn(); - - // THEN face auth is NOT started since we rely on STARTED_WAKING_UP to start face auth, - // NOT the display on event - verifyFaceAuthenticateNeverCalled(); - verifyFaceDetectNeverCalled(); - } - - @Test - public void onDisplayOff_stopFaceAuth() throws RemoteException { - enableStopFaceAuthOnDisplayOff(); - - // GIVEN device is listening for face - mKeyguardUpdateMonitor.setKeyguardShowing(true, false); - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - verifyFaceAuthenticateCall(); - - final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); - mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; - KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); - mKeyguardUpdateMonitor.registerCallback(callback); - - // WHEN the default display state changes to OFF - triggerDefaultDisplayStateChangeToOff(); - - // THEN face listening is stopped. - verify(faceCancel).cancel(); - verify(callback).onBiometricRunningStateChanged( - eq(false), eq(BiometricSourceType.FACE)); - } - - @Test - public void onDisplayOff_whileAsleep_doesNotStopFaceAuth() throws RemoteException { - enableStopFaceAuthOnDisplayOff(); - when(mWakefulness.getWakefulness()).thenReturn(WAKEFULNESS_ASLEEP); - - // GIVEN device is listening for face - mKeyguardUpdateMonitor.setKeyguardShowing(true, false); - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - verifyFaceAuthenticateCall(); - - final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); - mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; - KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); - mKeyguardUpdateMonitor.registerCallback(callback); - // WHEN the default display state changes to OFF - triggerDefaultDisplayStateChangeToOff(); - - // THEN face listening is NOT stopped. - verify(faceCancel, never()).cancel(); - verify(callback, never()).onBiometricRunningStateChanged( - eq(false), eq(BiometricSourceType.FACE)); - } - - @Test - public void onDisplayOff_whileWaking_doesNotStopFaceAuth() throws RemoteException { - enableStopFaceAuthOnDisplayOff(); - when(mWakefulness.getWakefulness()).thenReturn(WAKEFULNESS_WAKING); - - // GIVEN device is listening for face - mKeyguardUpdateMonitor.setKeyguardShowing(true, false); - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + clearInvocations(callback); + mFaceAuthenticationListener.getValue().onAuthEnrollmentStateChanged(false); mTestableLooper.processAllMessages(); - verifyFaceAuthenticateCall(); - - final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); - mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; - KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); - mKeyguardUpdateMonitor.registerCallback(callback); - - // WHEN the default display state changes to OFF - triggerDefaultDisplayStateChangeToOff(); - - // THEN face listening is NOT stopped. - verify(faceCancel, never()).cancel(); - verify(callback, never()).onBiometricRunningStateChanged( - eq(false), eq(BiometricSourceType.FACE)); - } - - private void triggerDefaultDisplayStateChangeToOn() { - triggerDefaultDisplayStateChangeTo(true); - } - - private void triggerDefaultDisplayStateChangeToOff() { - triggerDefaultDisplayStateChangeTo(false); - } - - /** - * @param on true for Display.STATE_ON, else Display.STATE_OFF - */ - private void triggerDefaultDisplayStateChangeTo(boolean on) { - DisplayManagerGlobal displayManagerGlobal = mock(DisplayManagerGlobal.class); - DisplayInfo displayInfoWithDisplayState = new DisplayInfo(); - displayInfoWithDisplayState.state = on ? Display.STATE_ON : Display.STATE_OFF; - when(displayManagerGlobal.getDisplayInfo(mDisplayTracker.getDefaultDisplayId())) - .thenReturn(displayInfoWithDisplayState); - mDisplayTracker.setAllDisplays(new Display[]{ - new Display( - displayManagerGlobal, - mDisplayTracker.getDefaultDisplayId(), - displayInfoWithDisplayState, - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS - ) - }); - mDisplayTracker.triggerOnDisplayChanged(mDisplayTracker.getDefaultDisplayId()); + verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE); } private void verifyFingerprintAuthenticateNeverCalled() { @@ -3151,37 +2095,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(mFingerprintManager).detectFingerprint(any(), any(), any()); } - private void verifyFaceAuthenticateNeverCalled() { - verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), any()); - verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt()); - } - - private void verifyFaceAuthenticateCall() { - verify(mFaceManager).authenticate(any(), any(), any(), any(), any()); - } - - private void verifyFaceDetectNeverCalled() { - verify(mFaceManager, never()).detectFace(any(), any(), any()); - } - - private void verifyFaceDetectCall() { - verify(mFaceManager).detectFace(any(), any(), any()); - } - private void userDeviceLockDown() { when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId)) .thenReturn(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); } - private void supportsFaceDetection() throws RemoteException { - final boolean isClass3 = !mFaceSensorProperties.isEmpty() - && mFaceSensorProperties.get(0).sensorStrength == STRENGTH_STRONG; - mFaceSensorProperties = - List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true, isClass3)); - mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); - } - private void lockscreenBypassIsAllowed() { mockCanBypassLockscreen(true); } @@ -3204,38 +2123,20 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void faceAuthLockOut() { - mKeyguardUpdateMonitor.mFaceAuthenticationCallback - .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, ""); + when(mFaceAuthInteractor.isLockedOut()).thenReturn(true); + mFaceAuthenticationListener.getValue().onAuthenticationStatusChanged( + new ErrorFaceAuthenticationStatus(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "", + 0L)); } private void statusBarShadeIsNotLocked() { mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); } - private void statusBarShadeIsLocked() { - mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED); - } - private void keyguardIsVisible() { mKeyguardUpdateMonitor.setKeyguardShowing(true, false); } - private void triggerAuthInterrupt() { - mKeyguardUpdateMonitor.onAuthInterruptDetected(true); - } - - private void occludingAppRequestsFaceAuth() { - mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true); - } - - private void secureCameraLaunched() { - mKeyguardUpdateMonitor.onCameraLaunched(); - } - - private void userCurrentlySwitching() { - mKeyguardUpdateMonitor.setSwitchingUser(true); - } - private void fingerprintErrorTemporaryLockOut() { mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out"); @@ -3245,100 +2146,25 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_OPENED); } - private void deviceInPostureStateClosed() { - mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_CLOSED); - } - - private void successfulFingerprintAuth() { - mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback - .onAuthenticationSucceeded( - new FingerprintManager.AuthenticationResult(null, - null, - mCurrentUserId, - true)); - } - - private void triggerSuccessfulFaceAuth() { - mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN); - verify(mFaceManager).authenticate(any(), - any(), - mAuthenticationCallbackCaptor.capture(), - any(), - any()); - mAuthenticationCallbackCaptor.getValue() - .onAuthenticationSucceeded( - new FaceManager.AuthenticationResult(null, null, mCurrentUserId, false)); - } - private void currentUserIsSystem() { when(mUserManager.isSystemUser()).thenReturn(true); } - private void biometricsNotDisabledThroughDevicePolicyManager() { - when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, - mSelectedUserInteractor.getSelectedUserId())).thenReturn(0); - } - private void biometricsEnabledForCurrentUser() throws RemoteException { mBiometricEnabledOnKeyguardCallback.onChanged(true, mSelectedUserInteractor.getSelectedUserId()); } - private void biometricsDisabledForCurrentUser() throws RemoteException { - mBiometricEnabledOnKeyguardCallback.onChanged( - false, - mSelectedUserInteractor.getSelectedUserId() - ); - } - - private void primaryAuthRequiredEncrypted() { - when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId())) - .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT); - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); - } - - private void primaryAuthRequiredForWeakBiometricOnly() { - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true); - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false); - } - private void primaryAuthNotRequiredByStrongAuthTracker() { when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId())) .thenReturn(0); when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); } - private void currentUserDoesNotHaveTrust() { - mKeyguardUpdateMonitor.onTrustChanged( - false, - false, - mSelectedUserInteractor.getSelectedUserId(), - -1, - new ArrayList<>() - ); - } - - private void userNotCurrentlySwitching() { - mKeyguardUpdateMonitor.setSwitchingUser(false); - } - private void keyguardNotGoingAway() { mKeyguardUpdateMonitor.setKeyguardGoingAway(false); } - private void bouncerFullyVisibleAndNotGoingToSleep() { - bouncerFullyVisible(); - deviceNotGoingToSleep(); - } - - private void deviceNotGoingToSleep() { - mKeyguardUpdateMonitor.dispatchFinishedGoingToSleep(/* value doesn't matter */1); - } - - private void deviceGoingToSleep() { - mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(/* value doesn't matter */1); - } - private void deviceIsInteractive() { mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); } @@ -3347,20 +2173,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { setKeyguardBouncerVisibility(true); } - private void bouncerNotVisible() { - setKeyguardBouncerVisibility(false); - } - private void setKeyguardBouncerVisibility(boolean isVisible) { mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible); mTestableLooper.processAllMessages(); } - private void givenUdfpsSupported() { - when(mAuthController.isUdfpsSupported()).thenReturn(true); - Assert.assertTrue(mKeyguardUpdateMonitor.isUdfpsSupported()); - } - private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) { BroadcastReceiver.PendingResult pendingResult = new BroadcastReceiver.PendingResult(Activity.RESULT_OK, @@ -3386,31 +2203,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); } - private void givenDetectFace() throws RemoteException { - // GIVEN bypass is enabled, face detection is supported and primary auth is required - lockscreenBypassIsAllowed(); - supportsFaceDetection(); - primaryAuthRequiredEncrypted(); - keyguardIsVisible(); - // fingerprint is NOT running, UDFPS is NOT supported - - // WHEN the device wakes up - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - } - - private void enableStopFaceAuthOnDisplayOff() throws RemoteException { - cleanupKeyguardUpdateMonitor(); - clearInvocations(mFaceManager); - clearInvocations(mFingerprintManager); - clearInvocations(mBiometricManager); - clearInvocations(mStatusBarStateController); - mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); - setupBiometrics(mKeyguardUpdateMonitor); - when(mWakefulness.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE); - assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(1); - } - private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) { int subscription = simInited ? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE; @@ -3486,11 +2278,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker, mTrustManager, mSubscriptionManager, mUserManager, mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager, - mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager, + mPackageManager, mFingerprintManager, mBiometricManager, mFaceWakeUpTriggersConfig, mDevicePostureController, Optional.of(mInteractiveToAuthProvider), - mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker, - mWakefulness, mSelectedUserInteractor); + mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index d2f45ae8685a..3d7d701ee5d6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -18,7 +18,6 @@ package com.android.keyguard; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; -import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; @@ -151,7 +150,6 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); mFeatureFlags = new FakeFeatureFlags(); - mFeatureFlags.set(FACE_AUTH_REFACTOR, false); mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); mFeatureFlags.set(LOCKSCREEN_ENABLE_LANDSCAPE, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt index ea7cc3dcd119..6c91c987f327 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt @@ -100,7 +100,7 @@ class FaceScanningProviderFactoryTest : SysuiTestCase() { @Test fun shouldNotShowFaceScanningAnimationIfFaceIsNotEnrolled() { - whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false) + whenever(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(false) whenever(authController.isShowing).thenReturn(true) assertThat(underTest.shouldShowFaceScanningAnim()).isFalse() @@ -108,7 +108,7 @@ class FaceScanningProviderFactoryTest : SysuiTestCase() { @Test fun shouldShowFaceScanningAnimationIfBiometricPromptIsShowing() { - whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) + whenever(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true) whenever(authController.isShowing).thenReturn(true) assertThat(underTest.shouldShowFaceScanningAnim()).isTrue() @@ -116,7 +116,7 @@ class FaceScanningProviderFactoryTest : SysuiTestCase() { @Test fun shouldShowFaceScanningAnimationIfKeyguardFaceDetectionIsShowing() { - whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) + whenever(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true) whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true) assertThat(underTest.shouldShowFaceScanningAnim()).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index 11c5d3bb27b3..602f3dc29491 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -475,6 +475,22 @@ public class AuthControllerTest extends SysuiTestCase { } @Test + public void testOnAuthenticationFailedInvoked_whenBiometricReEnrollRequired() { + showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); + final int modality = BiometricAuthenticator.TYPE_FACE; + mAuthController.onBiometricError(modality, + BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL, + 0 /* vendorCode */); + + verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), + mMessageCaptor.capture()); + + assertThat(mModalityCaptor.getValue()).isEqualTo(modality); + assertThat(mMessageCaptor.getValue()).isEqualTo(mContext.getString( + R.string.face_recalibrate_notification_content)); + } + + @Test public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withPaused() { testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected( BiometricConstants.BIOMETRIC_PAUSED_REJECTED); 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 00ea78f01fa9..993dbac747e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt @@ -65,10 +65,7 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { .create( test = this, featureFlags = - FakeFeatureFlagsClassicModule { - set(Flags.FACE_AUTH_REFACTOR, false) - set(Flags.FULL_SCREEN_USER_SWITCHER, true) - }, + FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, ) private val detector: AuthDialogPanelInteractionDetector = testComponent.underTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt index ec17794d4ee2..86b9b84ab36e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt @@ -21,13 +21,10 @@ import android.view.View import android.view.accessibility.AccessibilityNodeInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.keyguard.FaceAuthApiRequestReason -import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor -import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import org.junit.Assert.assertEquals import org.junit.Before @@ -44,7 +41,6 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper class FaceAuthAccessibilityDelegateTest : SysuiTestCase() { - @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var hostView: View @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor private lateinit var underTest: FaceAuthAccessibilityDelegate @@ -55,14 +51,13 @@ class FaceAuthAccessibilityDelegateTest : SysuiTestCase() { underTest = FaceAuthAccessibilityDelegate( context.resources, - keyguardUpdateMonitor, faceAuthInteractor, ) } @Test fun shouldListenForFaceTrue_onInitializeAccessibilityNodeInfo_clickActionAdded() { - whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true) + whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(true) // WHEN node is initialized val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java) @@ -81,7 +76,7 @@ class FaceAuthAccessibilityDelegateTest : SysuiTestCase() { @Test fun shouldListenForFaceFalse_onInitializeAccessibilityNodeInfo_clickActionNotAdded() { - whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false) + whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(false) // WHEN node is initialized val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java) @@ -94,7 +89,7 @@ class FaceAuthAccessibilityDelegateTest : SysuiTestCase() { @Test fun performAccessibilityAction_actionClick_retriesFaceAuth() { - whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true) + whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(true) // WHEN click action is performed underTest.performAccessibilityAction( @@ -103,9 +98,6 @@ class FaceAuthAccessibilityDelegateTest : SysuiTestCase() { null ) - // THEN retry face auth - verify(keyguardUpdateMonitor) - .requestFaceAuth(eq(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION)) verify(faceAuthInteractor).onAccessibilityAction() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 5f0d4d428322..f5b6f14a627c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -36,13 +36,13 @@ import android.view.accessibility.AccessibilityManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R @@ -107,7 +107,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsView: UdfpsView @Mock private lateinit var mUdfpsKeyguardViewLegacy: UdfpsKeyguardViewLegacy @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator - @Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @@ -123,47 +122,52 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Before fun setup() { whenever(inflater.inflate(R.layout.udfps_view, null, false)) - .thenReturn(udfpsView) + .thenReturn(udfpsView) whenever(inflater.inflate(R.layout.udfps_bp_view, null)) - .thenReturn(mock(UdfpsBpView::class.java)) + .thenReturn(mock(UdfpsBpView::class.java)) whenever(inflater.inflate(R.layout.udfps_keyguard_view_legacy, null)) - .thenReturn(mUdfpsKeyguardViewLegacy) + .thenReturn(mUdfpsKeyguardViewLegacy) whenever(inflater.inflate(R.layout.udfps_fpm_empty_view, null)) - .thenReturn(mock(UdfpsFpmEmptyView::class.java)) + .thenReturn(mock(UdfpsFpmEmptyView::class.java)) } private fun withReason( - @ShowReason reason: Int, - isDebuggable: Boolean = false, - block: () -> Unit + @ShowReason reason: Int, + isDebuggable: Boolean = false, + enableDeviceEntryUdfpsRefactor: Boolean = false, + block: () -> Unit, ) { + if (enableDeviceEntryUdfpsRefactor) { + mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + } else { + mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + } controllerOverlay = UdfpsControllerOverlay( - context, - inflater, - windowManager, - accessibilityManager, - statusBarStateController, - statusBarKeyguardViewManager, - keyguardUpdateMonitor, - dialogManager, - dumpManager, - transitionController, - configurationController, - keyguardStateController, - unlockedScreenOffAnimationController, - udfpsDisplayMode, - REQUEST_ID, - reason, - controllerCallback, - onTouch, - activityLaunchAnimator, - featureFlags, - primaryBouncerInteractor, - alternateBouncerInteractor, - isDebuggable, - udfpsKeyguardAccessibilityDelegate, - keyguardTransitionInteractor, - mSelectedUserInteractor, + context, + inflater, + windowManager, + accessibilityManager, + statusBarStateController, + statusBarKeyguardViewManager, + keyguardUpdateMonitor, + dialogManager, + dumpManager, + transitionController, + configurationController, + keyguardStateController, + unlockedScreenOffAnimationController, + udfpsDisplayMode, + REQUEST_ID, + reason, + controllerCallback, + onTouch, + activityLaunchAnimator, + primaryBouncerInteractor, + alternateBouncerInteractor, + isDebuggable, + udfpsKeyguardAccessibilityDelegate, + keyguardTransitionInteractor, + mSelectedUserInteractor, ) block() } @@ -185,12 +189,12 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT) val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT) overlayParams = UdfpsOverlayParams( - sensorBounds, - overlayBounds, - DISPLAY_WIDTH, - DISPLAY_HEIGHT, - scaleFactor = 1f, - rotation + sensorBounds, + overlayBounds, + DISPLAY_WIDTH, + DISPLAY_HEIGHT, + scaleFactor = 1f, + rotation ) block() } @@ -200,8 +204,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { withReason(REASON_AUTH_BP) { controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() + eq(controllerOverlay.getTouchOverlay()), + layoutParamsCaptor.capture() ) // ROTATION_0 is the native orientation. Sensor should stay in the top left corner. @@ -218,8 +222,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { withReason(REASON_AUTH_BP) { controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() + eq(controllerOverlay.getTouchOverlay()), + layoutParamsCaptor.capture() ) // ROTATION_180 is not supported. Sensor should stay in the top left corner. @@ -236,8 +240,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { withReason(REASON_AUTH_BP) { controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() + eq(controllerOverlay.getTouchOverlay()), + layoutParamsCaptor.capture() ) // Sensor should be in the bottom left corner in ROTATION_90. @@ -254,8 +258,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { withReason(REASON_AUTH_BP) { controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() + eq(controllerOverlay.getTouchOverlay()), + layoutParamsCaptor.capture() ) // Sensor should be in the top right corner in ROTATION_270. @@ -270,7 +274,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { private fun showUdfpsOverlay() { val didShow = controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager).addView(eq(controllerOverlay.overlayView), any()) + verify(windowManager).addView(eq(controllerOverlay.getTouchOverlay()), any()) verify(udfpsView).setUdfpsDisplayModeProvider(eq(udfpsDisplayMode)) verify(udfpsView).animationViewController = any() verify(udfpsView).addView(any()) @@ -278,7 +282,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { assertThat(didShow).isTrue() assertThat(controllerOverlay.isShowing).isTrue() assertThat(controllerOverlay.isHiding).isFalse() - assertThat(controllerOverlay.overlayView).isNotNull() + assertThat(controllerOverlay.getTouchOverlay()).isNotNull() } @Test @@ -295,14 +299,14 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { private fun hideUdfpsOverlay() { val didShow = controllerOverlay.show(udfpsController, overlayParams) - val view = controllerOverlay.overlayView + val view = controllerOverlay.getTouchOverlay() val didHide = controllerOverlay.hide() verify(windowManager).removeView(eq(view)) assertThat(didShow).isTrue() assertThat(didHide).isTrue() - assertThat(controllerOverlay.overlayView).isNull() + assertThat(controllerOverlay.getTouchOverlay()).isNull() assertThat(controllerOverlay.animationViewController).isNull() assertThat(controllerOverlay.isShowing).isFalse() assertThat(controllerOverlay.isHiding).isTrue() @@ -348,8 +352,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() + eq(controllerOverlay.getTouchOverlay()), + layoutParamsCaptor.capture() ) // Layout params should use sensor bounds diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index c8c400de5740..e2cab29c473c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -73,6 +73,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; @@ -286,6 +287,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // Create a fake background executor. mBiometricExecutor = new FakeExecutor(new FakeSystemClock()); + mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); initUdfpsController(mOpticalProps); } @@ -304,7 +306,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mStatusBarKeyguardViewManager, mDumpManager, mKeyguardUpdateMonitor, - mFeatureFlags, mFalsingManager, mPowerManager, mAccessibilityManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index 9bff88bbf2dd..79f062536404 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState @@ -106,6 +107,7 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : FakeTrustRepository(), testScope.backgroundScope, mSelectedUserInteractor, + mock(KeyguardFaceAuthInteractor::class.java), ) mAlternateBouncerInteractor = AlternateBouncerInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt index b48bc1d28f86..094616f0682c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt @@ -43,6 +43,7 @@ import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsReposi import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.keyguard.shared.model.AuthenticationFlags import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown import com.android.systemui.res.R.string.kg_trust_agent_disabled @@ -62,6 +63,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -122,6 +124,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { fakeTrustRepository, testScope.backgroundScope, mSelectedUserInteractor, + mock(KeyguardFaceAuthInteractor::class.java), ) underTest = BouncerMessageInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt index d6aa9ac87d39..37a093e4c23f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.bouncer.domain.interactor -import android.hardware.biometrics.BiometricSourceType import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.testing.TestableResources @@ -35,6 +34,7 @@ import com.android.systemui.bouncer.ui.BouncerViewDelegate import com.android.systemui.classifier.FalsingCollector import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.policy.KeyguardStateController @@ -72,6 +72,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor private lateinit var mainHandler: FakeHandler private lateinit var underTest: PrimaryBouncerInteractor private lateinit var resources: TestableResources @@ -103,6 +104,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { trustRepository, testScope.backgroundScope, mSelectedUserInteractor, + faceAuthInteractor, ) whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) whenever(repository.primaryBouncerShow.value).thenReturn(false) @@ -423,10 +425,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { mainHandler.setMode(FakeHandler.Mode.QUEUEING) // GIVEN bouncer should be delayed due to face auth - whenever(keyguardStateController.isFaceEnrolled).thenReturn(true) - whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)) - .thenReturn(true) - whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(true) + whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(true) // WHEN bouncer show is requested underTest.show(true) @@ -444,15 +443,12 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { } @Test - fun noDelayBouncer_biometricsAllowed_postureDoesNotAllowFaceAuth() { + fun noDelayBouncer_faceAuthNotAllowed() { mainHandler.setMode(FakeHandler.Mode.QUEUEING) // GIVEN bouncer should not be delayed because device isn't in the right posture for // face auth - whenever(keyguardStateController.isFaceEnrolled).thenReturn(true) - whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)) - .thenReturn(true) - whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(false) + whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(false) // WHEN bouncer show is requested underTest.show(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt index d1b120e0948b..bdf5041f8a38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.utils.os.FakeHandler @@ -53,6 +54,7 @@ class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() { @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor private val mainHandler = FakeHandler(Looper.getMainLooper()) private lateinit var underTest: PrimaryBouncerInteractor @@ -75,6 +77,7 @@ class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() { Mockito.mock(TrustRepository::class.java), TestScope().backgroundScope, mSelectedUserInteractor, + faceAuthInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt new file mode 100644 index 000000000000..395d7129bee3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.ui.helper + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD +import com.google.common.truth.Truth.assertThat +import java.util.Locale +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@SmallTest +@RunWith(Parameterized::class) +class BouncerSceneLayoutTest : SysuiTestCase() { + + data object Phone : + Device( + name = "phone", + width = SizeClass.COMPACT, + height = SizeClass.EXPANDED, + naturallyHeld = Vertically, + ) + data object Tablet : + Device( + name = "tablet", + width = SizeClass.EXPANDED, + height = SizeClass.MEDIUM, + naturallyHeld = Horizontally, + ) + data object Folded : + Device( + name = "folded", + width = SizeClass.COMPACT, + height = SizeClass.MEDIUM, + naturallyHeld = Vertically, + ) + data object Unfolded : + Device( + name = "unfolded", + width = SizeClass.EXPANDED, + height = SizeClass.MEDIUM, + naturallyHeld = Vertically, + widthWhenUnnaturallyHeld = SizeClass.MEDIUM, + heightWhenUnnaturallyHeld = SizeClass.MEDIUM, + ) + data object TallerFolded : + Device( + name = "taller folded", + width = SizeClass.COMPACT, + height = SizeClass.EXPANDED, + naturallyHeld = Vertically, + ) + data object TallerUnfolded : + Device( + name = "taller unfolded", + width = SizeClass.EXPANDED, + height = SizeClass.EXPANDED, + naturallyHeld = Vertically, + ) + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun testCases() = + listOf( + Phone to + Expected( + whenNaturallyHeld = STANDARD, + whenUnnaturallyHeld = SPLIT, + ), + Tablet to + Expected( + whenNaturallyHeld = SIDE_BY_SIDE, + whenUnnaturallyHeld = STACKED, + ), + Folded to + Expected( + whenNaturallyHeld = STANDARD, + whenUnnaturallyHeld = SPLIT, + ), + Unfolded to + Expected( + whenNaturallyHeld = SIDE_BY_SIDE, + whenUnnaturallyHeld = STANDARD, + ), + TallerFolded to + Expected( + whenNaturallyHeld = STANDARD, + whenUnnaturallyHeld = SPLIT, + ), + TallerUnfolded to + Expected( + whenNaturallyHeld = SIDE_BY_SIDE, + whenUnnaturallyHeld = SIDE_BY_SIDE, + ), + ) + .flatMap { (device, expected) -> + buildList { + // Holding the device in its natural orientation (vertical or horizontal): + add( + TestCase( + device = device, + held = device.naturallyHeld, + expected = expected.layout(heldNaturally = true), + ) + ) + + if (expected.whenNaturallyHeld == SIDE_BY_SIDE) { + add( + TestCase( + device = device, + held = device.naturallyHeld, + isSideBySideSupported = false, + expected = STANDARD, + ) + ) + } + + // Holding the device the other way: + add( + TestCase( + device = device, + held = device.naturallyHeld.flip(), + expected = expected.layout(heldNaturally = false), + ) + ) + + if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) { + add( + TestCase( + device = device, + held = device.naturallyHeld.flip(), + isSideBySideSupported = false, + expected = STANDARD, + ) + ) + } + } + } + } + + @Parameterized.Parameter @JvmField var testCase: TestCase? = null + + @Test + fun calculateLayout() { + testCase?.let { nonNullTestCase -> + with(nonNullTestCase) { + assertThat( + calculateLayoutInternal( + width = device.width(whenHeld = held), + height = device.height(whenHeld = held), + isSideBySideSupported = isSideBySideSupported, + ) + ) + .isEqualTo(expected) + } + } + } + + data class TestCase( + val device: Device, + val held: Held, + val expected: BouncerSceneLayout, + val isSideBySideSupported: Boolean = true, + ) { + override fun toString(): String { + return buildString { + append(device.name) + append(" width: ${device.width(held).name.lowercase(Locale.US)}") + append(" height: ${device.height(held).name.lowercase(Locale.US)}") + append(" when held $held") + if (!isSideBySideSupported) { + append(" (side-by-side not supported)") + } + } + } + } + + data class Expected( + val whenNaturallyHeld: BouncerSceneLayout, + val whenUnnaturallyHeld: BouncerSceneLayout, + ) { + fun layout(heldNaturally: Boolean): BouncerSceneLayout { + return if (heldNaturally) { + whenNaturallyHeld + } else { + whenUnnaturallyHeld + } + } + } + + sealed class Device( + val name: String, + private val width: SizeClass, + private val height: SizeClass, + val naturallyHeld: Held, + private val widthWhenUnnaturallyHeld: SizeClass = height, + private val heightWhenUnnaturallyHeld: SizeClass = width, + ) { + fun width(whenHeld: Held): SizeClass { + return if (isHeldNaturally(whenHeld)) { + width + } else { + widthWhenUnnaturallyHeld + } + } + + fun height(whenHeld: Held): SizeClass { + return if (isHeldNaturally(whenHeld)) { + height + } else { + heightWhenUnnaturallyHeld + } + } + + private fun isHeldNaturally(whenHeld: Held): Boolean { + return whenHeld == naturallyHeld + } + } + + sealed class Held { + abstract fun flip(): Held + } + data object Vertically : Held() { + override fun flip(): Held { + return Horizontally + } + } + data object Horizontally : Held() { + override fun flip(): Held { + return Vertically + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt index 2cc8f0a47955..90e0c19b7c65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.TrustRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.utils.os.FakeHandler @@ -61,6 +62,7 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor lateinit var bouncerInteractor: PrimaryBouncerInteractor private val mainHandler = FakeHandler(Looper.getMainLooper()) @@ -86,6 +88,7 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() { Mockito.mock(TrustRepository::class.java), TestScope().backgroundScope, mSelectedUserInteractor, + faceAuthInteractor, ) underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 6c990e457209..40c9432d543d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -30,7 +30,6 @@ import android.testing.TestableLooper import android.view.SurfaceControlViewHost import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils -import com.android.systemui.res.R import com.android.systemui.SystemUIAppComponentFactoryBase import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator @@ -51,6 +50,7 @@ import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRendererFactory import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract @@ -173,7 +173,6 @@ class CustomizationProviderTest : SysuiTestCase() { FakeFeatureFlags().apply { set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true) set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true) - set(Flags.FACE_AUTH_REFACTOR, true) } underTest.interactor = KeyguardQuickAffordanceInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index f0ff77ebf1ea..852f9a540221 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -52,7 +52,6 @@ class ResourceTrimmerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) featureFlags.set(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK, true) featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true) - featureFlags.set(Flags.FACE_AUTH_REFACTOR, false) powerInteractor = PowerInteractorFactory.create().powerInteractor keyguardRepository.setDozeAmount(0f) keyguardRepository.setKeyguardGoingAway(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 0b148d14a43f..45aca175657e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -58,7 +58,6 @@ import com.android.systemui.display.data.repository.display import com.android.systemui.dump.DumpManager import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory @@ -188,11 +187,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { biometricSettingsRepository = FakeBiometricSettingsRepository() deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() trustRepository = FakeTrustRepository() - featureFlags = - FakeFeatureFlags().apply { - set(FACE_AUTH_REFACTOR, true) - set(KEYGUARD_WM_STATE_REFACTOR, false) - } + featureFlags = FakeFeatureFlags().apply { set(KEYGUARD_WM_STATE_REFACTOR, false) } powerRepository = FakePowerRepository() powerInteractor = @@ -790,21 +785,19 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun everythingWorksWithFaceAuthRefactorFlagDisabled() = + fun everythingEmitsADefaultValueAndDoesNotErrorOut() = testScope.runTest { - featureFlags.set(FACE_AUTH_REFACTOR, false) - underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() // Collecting any flows exposed in the public API doesn't throw any error - authStatus() - detectStatus() - authRunning() - bypassEnabled() - lockedOut() - canFaceAuthRun() - authenticated() + assertThat(authStatus()).isNull() + assertThat(detectStatus()).isNull() + assertThat(authRunning()).isNotNull() + assertThat(bypassEnabled()).isNotNull() + assertThat(lockedOut()).isNotNull() + assertThat(canFaceAuthRun()).isNotNull() + assertThat(authenticated()).isNotNull() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index f2de8caef176..b3e8fed24c3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -41,8 +41,6 @@ import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dump.logcatLogBuffer -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository @@ -109,8 +107,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { val scheduler = TestCoroutineScheduler() val dispatcher = StandardTestDispatcher(scheduler) testScope = TestScope(dispatcher) - val featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.FACE_AUTH_REFACTOR, true) bouncerRepository = FakeKeyguardBouncerRepository() faceAuthRepository = FakeDeviceEntryFaceAuthRepository() keyguardTransitionRepository = FakeKeyguardTransitionRepository() @@ -135,21 +131,24 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { testScope.backgroundScope, dispatcher, faceAuthRepository, - PrimaryBouncerInteractor( - bouncerRepository, - mock(BouncerView::class.java), - mock(Handler::class.java), - mock(KeyguardStateController::class.java), - mock(KeyguardSecurityModel::class.java), - mock(PrimaryBouncerCallbackInteractor::class.java), - mock(FalsingCollector::class.java), - mock(DismissCallbackRegistry::class.java), - context, - keyguardUpdateMonitor, - FakeTrustRepository(), - testScope.backgroundScope, - mSelectedUserInteractor, - ), + { + PrimaryBouncerInteractor( + bouncerRepository, + mock(BouncerView::class.java), + mock(Handler::class.java), + mock(KeyguardStateController::class.java), + mock(KeyguardSecurityModel::class.java), + mock(PrimaryBouncerCallbackInteractor::class.java), + mock(FalsingCollector::class.java), + mock(DismissCallbackRegistry::class.java), + context, + keyguardUpdateMonitor, + FakeTrustRepository(), + testScope.backgroundScope, + mSelectedUserInteractor, + underTest, + ) + }, AlternateBouncerInteractor( mock(StatusBarStateController::class.java), mock(KeyguardStateController::class.java), @@ -161,7 +160,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { testScope.backgroundScope, ), keyguardTransitionInteractor, - featureFlags, FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")), keyguardUpdateMonitor, fakeDeviceEntryFingerprintAuthRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index ad2ec72468ad..706f94e412ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -24,8 +24,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.power.domain.interactor.PowerInteractorFactory @@ -54,7 +52,6 @@ class KeyguardInteractorTest : SysuiTestCase() { private val repository = testUtils.keyguardRepository private val sceneInteractor = testUtils.sceneInteractor() private val commandQueue = FakeCommandQueue() - private val featureFlags = FakeFeatureFlagsClassic().apply { set(FACE_AUTH_REFACTOR, true) } private val bouncerRepository = FakeKeyguardBouncerRepository() private val configurationRepository = FakeConfigurationRepository() private val shadeRepository = FakeShadeRepository() @@ -66,7 +63,6 @@ class KeyguardInteractorTest : SysuiTestCase() { repository = repository, commandQueue = commandQueue, powerInteractor = PowerInteractorFactory.create().powerInteractor, - featureFlags = featureFlags, sceneContainerFlags = testUtils.sceneContainerFlags, bouncerRepository = bouncerRepository, configurationRepository = configurationRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index fe474faa877d..66c8a229f0a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -31,7 +31,6 @@ import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dock.DockManagerFake import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory @@ -302,10 +301,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { dumpManager = mock(), userHandle = UserHandle.SYSTEM, ) - val featureFlags = - FakeFeatureFlags().apply { - set(Flags.FACE_AUTH_REFACTOR, true) - } + val featureFlags = FakeFeatureFlags() val testDispatcher = StandardTestDispatcher() testScope = TestScope(testDispatcher) underTest = @@ -357,7 +353,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { } underTest.onQuickAffordanceTriggered( - configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::${key}", + configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::$key", expandable = expandable, slotId = "", ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 347d580abf5b..bc4bae0ed959 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -22,7 +22,6 @@ import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.common.shared.model.ContentDescription @@ -31,7 +30,6 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dock.DockManager import com.android.systemui.dock.DockManagerFake import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory @@ -48,6 +46,7 @@ import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots @@ -102,9 +101,11 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { overrideResource( R.array.config_keyguardQuickAffordanceDefaults, arrayOf( - KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" + + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + + ":" + BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS, - KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" + + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + + ":" + BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET ) ) @@ -168,10 +169,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { dumpManager = mock(), userHandle = UserHandle.SYSTEM, ) - featureFlags = - FakeFeatureFlags().apply { - set(Flags.FACE_AUTH_REFACTOR, true) - } + featureFlags = FakeFeatureFlags() val withDeps = KeyguardInteractorFactory.create( @@ -216,9 +214,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(collectedValue()) .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java) val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible - assertThat(visibleModel.configKey).isEqualTo( - "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${configKey}" - ) + assertThat(visibleModel.configKey) + .isEqualTo("${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::$configKey") assertThat(visibleModel.icon).isEqualTo(ICON) assertThat(visibleModel.icon.contentDescription) .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID)) @@ -243,9 +240,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(collectedValue()) .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java) val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible - assertThat(visibleModel.configKey).isEqualTo( - "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END}::${configKey}" - ) + assertThat(visibleModel.configKey) + .isEqualTo("${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END}::$configKey") assertThat(visibleModel.icon).isEqualTo(ICON) assertThat(visibleModel.icon.contentDescription) .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID)) @@ -364,9 +360,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(collectedValue()) .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java) val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible - assertThat(visibleModel.configKey).isEqualTo( - "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${configKey}" - ) + assertThat(visibleModel.configKey) + .isEqualTo("${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::$configKey") assertThat(visibleModel.icon).isEqualTo(ICON) assertThat(visibleModel.icon.contentDescription) .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index bf23bf875ad3..bf6d5c4535ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -121,11 +121,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) - featureFlags = - FakeFeatureFlags().apply { - set(Flags.FACE_AUTH_REFACTOR, true) - set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) - } + featureFlags = FakeFeatureFlags().apply { set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) } keyguardInteractor = createKeyguardInteractor() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt index 91e17059bb3d..1f245f1e86f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt @@ -32,7 +32,6 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -103,7 +102,7 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() { keyguardRepository = FakeKeyguardRepository() bouncerRepository = FakeKeyguardBouncerRepository() configurationRepository = FakeConfigurationRepository() - featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) } + featureFlags = FakeFeatureFlags() trustRepository = FakeTrustRepository() powerRepository = FakePowerRepository() powerInteractor = @@ -147,7 +146,8 @@ class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() { keyguardUpdateMonitor, trustRepository, testScope.backgroundScope, - mSelectedUserInteractor + mSelectedUserInteractor, + keyguardFaceAuthInteractor = mock(), ), AlternateBouncerInteractor( statusBarStateController = mock(), 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 16d072e99964..87eee1a8d817 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 @@ -25,8 +25,6 @@ import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState @@ -64,7 +62,6 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { private lateinit var bouncerRepository: KeyguardBouncerRepository private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var fakeCommandQueue: FakeCommandQueue - private lateinit var featureFlags: FakeFeatureFlags private lateinit var burnInInteractor: BurnInInteractor private lateinit var shadeRepository: FakeShadeRepository private lateinit var keyguardInteractor: KeyguardInteractor @@ -80,8 +77,7 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testScope = TestScope() configRepository = FakeConfigurationRepository() - featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) } - KeyguardInteractorFactory.create(featureFlags = featureFlags).let { + KeyguardInteractorFactory.create().let { keyguardInteractor = it.keyguardInteractor keyguardRepository = it.repository } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 32d28a3d7bb2..1584be0ab565 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -22,7 +22,6 @@ import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.widget.LockPatternUtils -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.Expandable @@ -52,6 +51,7 @@ import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots @@ -116,9 +116,11 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { overrideResource( R.array.config_keyguardQuickAffordanceDefaults, arrayOf( - KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" + + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + + ":" + BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS, - KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" + + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + + ":" + BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET ) ) @@ -138,7 +140,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { biometricSettingsRepository = FakeBiometricSettingsRepository() val featureFlags = FakeFeatureFlags().apply { - set(Flags.FACE_AUTH_REFACTOR, true) set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index 25d141997734..67c4e2688cd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -22,14 +22,13 @@ import android.content.Intent import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils -import com.android.systemui.res.R +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dock.DockManagerFake -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys @@ -49,6 +48,7 @@ import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots @@ -129,7 +129,6 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { val featureFlags = FakeFeatureFlags().apply { - set(Flags.FACE_AUTH_REFACTOR, true) set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false) } 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 a838684a4f41..e6d6cf263d69 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 @@ -32,9 +32,7 @@ import com.android.systemui.common.ui.data.repository.FakeConfigurationRepositor 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.FakeFeatureFlagsClassic 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.BurnInInteractor @@ -109,9 +107,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() { mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - val featureFlags = FakeFeatureFlagsClassic().apply { set(Flags.FACE_AUTH_REFACTOR, true) } - - val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags) + val withDeps = KeyguardInteractorFactory.create() keyguardInteractor = withDeps.keyguardInteractor repository = withDeps.repository configurationRepository = withDeps.configurationRepository @@ -135,7 +131,6 @@ class KeyguardRootViewModelTest : SysuiTestCase() { deviceEntryInteractor = mock { whenever(isBypassEnabled).thenReturn(MutableStateFlow(false)) }, dozeParameters = mock(), - featureFlags, keyguardInteractor, keyguardTransitionInteractor, notificationsKeyguardInteractor = @@ -347,8 +342,7 @@ class KeyguardRootViewModelTestWithFakes : SysuiTestCase() { DaggerKeyguardRootViewModelTestWithFakes_TestComponent.factory() .create( test = this, - featureFlags = - FakeFeatureFlagsClassicModule { set(Flags.FACE_AUTH_REFACTOR, true) }, + featureFlags = FakeFeatureFlagsClassicModule(), mocks = TestMocksModule( dozeParameters = dozeParams, 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 index c50be04e8a9c..2314c8358ff8 100644 --- 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 @@ -28,7 +28,6 @@ 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 @@ -94,10 +93,7 @@ class LockscreenToAodTransitionViewModelTest : SysuiTestCase() { .create( test = this, featureFlags = - FakeFeatureFlagsClassicModule { - set(FACE_AUTH_REFACTOR, true) - set(FULL_SCREEN_USER_SWITCHER, true) - }, + FakeFeatureFlagsClassicModule { set(FULL_SCREEN_USER_SWITCHER, true) }, mocks = TestMocksModule(), ) 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 26704da1496f..baac51385ba1 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 @@ -85,10 +85,7 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { .create( test = this, featureFlags = - FakeFeatureFlagsClassicModule { - set(Flags.FACE_AUTH_REFACTOR, true) - set(Flags.FULL_SCREEN_USER_SWITCHER, true) - }, + FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, mocks = TestMocksModule(), ) @Test 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 ff3135a6ad98..29d8f082795a 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 @@ -85,10 +85,7 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { .create( test = this, featureFlags = - FakeFeatureFlagsClassicModule { - set(Flags.FACE_AUTH_REFACTOR, true) - set(Flags.FULL_SCREEN_USER_SWITCHER, true) - }, + FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, mocks = TestMocksModule(), ) 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 index 8afd8e4fd425..049e4e27e374 100644 --- 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 @@ -86,10 +86,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { .create( test = this, featureFlags = - FakeFeatureFlagsClassicModule { - set(Flags.FACE_AUTH_REFACTOR, true) - set(Flags.FULL_SCREEN_USER_SWITCHER, true) - }, + FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, mocks = TestMocksModule(), ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt index 5058b1686781..6512290bf556 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt @@ -23,8 +23,6 @@ import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -54,7 +52,6 @@ class UdfpsAodViewModelTest : SysuiTestCase() { private lateinit var configRepository: FakeConfigurationRepository private lateinit var bouncerRepository: KeyguardBouncerRepository private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var featureFlags: FakeFeatureFlags private lateinit var shadeRepository: FakeShadeRepository private lateinit var keyguardInteractor: KeyguardInteractor @@ -67,16 +64,12 @@ class UdfpsAodViewModelTest : SysuiTestCase() { overrideResource(com.android.systemui.res.R.dimen.lock_icon_padding, defaultPadding) testScope = TestScope() shadeRepository = FakeShadeRepository() - featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) } - KeyguardInteractorFactory.create( - featureFlags = featureFlags, - ) - .also { - keyguardInteractor = it.keyguardInteractor - keyguardRepository = it.repository - configRepository = it.configurationRepository - bouncerRepository = it.bouncerRepository - } + KeyguardInteractorFactory.create().also { + keyguardInteractor = it.keyguardInteractor + keyguardRepository = it.repository + configRepository = it.configurationRepository + bouncerRepository = it.bouncerRepository + } val udfpsKeyguardInteractor = UdfpsKeyguardInteractor( configRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt index f039f5302a3f..95b2fe554f0c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt @@ -24,8 +24,6 @@ import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository @@ -59,7 +57,6 @@ class UdfpsFingerprintViewModelTest : SysuiTestCase() { private lateinit var bouncerRepository: KeyguardBouncerRepository private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var fakeCommandQueue: FakeCommandQueue - private lateinit var featureFlags: FakeFeatureFlags private lateinit var transitionRepository: FakeKeyguardTransitionRepository private lateinit var shadeRepository: FakeShadeRepository @@ -75,14 +72,12 @@ class UdfpsFingerprintViewModelTest : SysuiTestCase() { keyguardRepository = FakeKeyguardRepository() bouncerRepository = FakeKeyguardBouncerRepository() fakeCommandQueue = FakeCommandQueue() - featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) } bouncerRepository = FakeKeyguardBouncerRepository() transitionRepository = FakeKeyguardTransitionRepository() shadeRepository = FakeShadeRepository() val keyguardInteractor = KeyguardInteractorFactory.create( repository = keyguardRepository, - featureFlags = featureFlags, ) .keyguardInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt index c1805dbf26ad..848a94b2a5d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt @@ -23,8 +23,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FakeFeatureFlags -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.BurnInInteractor @@ -75,7 +73,6 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { private lateinit var keyguardInteractor: KeyguardInteractor private lateinit var bouncerRepository: FakeKeyguardBouncerRepository private lateinit var shadeRepository: FakeShadeRepository - private lateinit var featureFlags: FakeFeatureFlags @Before fun setUp() { @@ -83,16 +80,12 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { testScope = TestScope() transitionRepository = FakeKeyguardTransitionRepository() shadeRepository = FakeShadeRepository() - featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) } - KeyguardInteractorFactory.create( - featureFlags = featureFlags, - ) - .also { - keyguardInteractor = it.keyguardInteractor - keyguardRepository = it.repository - configRepository = it.configurationRepository - bouncerRepository = it.bouncerRepository - } + KeyguardInteractorFactory.create().also { + keyguardInteractor = it.keyguardInteractor + keyguardRepository = it.repository + configRepository = it.configurationRepository + bouncerRepository = it.bouncerRepository + } val transitionInteractor = KeyguardTransitionInteractorFactory.create( diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt new file mode 100644 index 000000000000..db9e548e74c8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt @@ -0,0 +1,86 @@ +package com.android.systemui.qs + +import android.content.Context +import android.testing.AndroidTestingRunner +import android.view.KeyEvent +import android.view.View +import android.widget.Scroller +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class PagedTileLayoutTest : SysuiTestCase() { + + @Mock private lateinit var pageIndicator: PageIndicator + @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener> + + private lateinit var pageTileLayout: TestPagedTileLayout + private lateinit var scroller: Scroller + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + pageTileLayout = TestPagedTileLayout(mContext) + pageTileLayout.setPageIndicator(pageIndicator) + verify(pageIndicator).setOnKeyListener(captor.capture()) + setViewWidth(pageTileLayout, width = PAGE_WIDTH) + scroller = pageTileLayout.mScroller + } + + private fun setViewWidth(view: View, width: Int) { + view.left = 0 + view.right = width + } + + @Test + fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() { + pageTileLayout.currentPageIndex = 0 + + sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT) + + assertThat(scroller.isFinished).isFalse() // aka we're scrolling + assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH) + } + + @Test + fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() { + pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page + + sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT) + + assertThat(scroller.isFinished).isFalse() // aka we're scrolling + assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH) + } + + private fun sendUpEvent(keyCode: Int) { + val event = KeyEvent(KeyEvent.ACTION_UP, keyCode) + captor.value.onKey(pageIndicator, keyCode, event) + } + + /** + * Custom PagedTileLayout to easy mock "currentItem" i.e. currently visible page. Setting this + * up otherwise would require setting adapter etc + */ + class TestPagedTileLayout(context: Context) : PagedTileLayout(context, null) { + + var currentPageIndex: Int = 0 + + override fun getCurrentItem(): Int { + return currentPageIndex + } + } + + companion object { + const val PAGE_WIDTH = 200 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 5c325ae67369..42e27ba12f42 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.qs.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -39,14 +38,11 @@ import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsPro import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository import com.android.systemui.util.mockito.mock 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 -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class QuickSettingsSceneViewModelTest : SysuiTestCase() { @@ -90,15 +86,8 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { broadcastDispatcher = fakeBroadcastDispatcher, ) - val authenticationInteractor = utils.authenticationInteractor() - underTest = QuickSettingsSceneViewModel( - deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, notifications = utils.notificationsPlaceholderViewModel(), @@ -106,32 +95,6 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { } @Test - fun onContentClicked_deviceUnlocked_switchesToGone() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(true) - runCurrent() - - underTest.onContentClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) - runCurrent() - - underTest.onContentClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - } - - @Test fun destinationsNotCustomizing() = testScope.runTest { val destinations by collectLastValue(underTest.destinationScenes) 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 ba8a66637e8a..03878b7bcf45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -147,6 +147,7 @@ import com.android.systemui.statusbar.notification.ConversationNotificationManag import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -335,6 +336,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock private JavaAdapter mJavaAdapter; @Mock private CastController mCastController; @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; + @Mock private ActiveNotificationsInteractor mActiveNotificationsInteractor; @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm; @Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver; @@ -709,6 +711,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardInteractor, mActivityStarter, mSharedNotificationContainerInteractor, + mActiveNotificationsInteractor, mKeyguardViewConfigurator, mKeyguardFaceAuthInteractor, new ResourcesSplitShadeStateController(), @@ -783,6 +786,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardFaceAuthInteractor, mShadeRepository, mShadeInteractor, + mActiveNotificationsInteractor, mJavaAdapter, mCastController, new ResourcesSplitShadeStateController() 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 722fb2c75081..36b4435e2137 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -31,7 +31,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; @@ -56,7 +55,6 @@ import android.view.accessibility.AccessibilityNodeInfo; import androidx.constraintlayout.widget.ConstraintSet; 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; @@ -1075,33 +1073,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); verify(mKeyguardFaceAuthInteractor).onNotificationPanelClicked(); - verify(mUpdateMonitor).requestFaceAuth( - FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); - } - - @Test - public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() { - StatusBarStateController.StateListener statusBarStateListener = - mNotificationPanelViewController.getStatusBarStateListener(); - statusBarStateListener.onStateChanged(KEYGUARD); - mNotificationPanelViewController.setDozing(true, false); - - // This sets the dozing state that is read when onMiddleClicked is eventually invoked. - mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); - mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); - - verify(mUpdateMonitor, never()).requestFaceAuth(anyString()); - } - - @Test - public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() { - StatusBarStateController.StateListener statusBarStateListener = - mNotificationPanelViewController.getStatusBarStateListener(); - statusBarStateListener.onStateChanged(SHADE_LOCKED); - - mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); - - verify(mUpdateMonitor, never()).requestFaceAuth(anyString()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 8403ac59d2f5..39b306b781f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -188,7 +188,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { keyguardRepository, new FakeCommandQueue(), powerInteractor, - featureFlags, sceneContainerFlags, new FakeKeyguardBouncerRepository(), configurationRepository, 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 d89491c7b442..0587633b09cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -65,6 +65,7 @@ import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsReposi import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionStep @@ -97,6 +98,7 @@ 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 java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.TestScope @@ -111,9 +113,8 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import java.util.Optional import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -265,6 +266,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { FakeTrustRepository(), testScope.backgroundScope, mSelectedUserInteractor, + mock(KeyguardFaceAuthInteractor::class.java) ), facePropertyRepository = FakeFacePropertyRepository(), deviceEntryFingerprintAuthRepository = 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 9c8816c72fae..29b1366eb6c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -251,6 +251,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { FakeTrustRepository(), testScope.backgroundScope, mSelectedUserInteractor, + mock(), ), facePropertyRepository = FakeFacePropertyRepository(), deviceEntryFingerprintAuthRepository = 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 26b84e372d5c..1dbb2972c6f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -80,6 +80,8 @@ import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor; @@ -178,6 +180,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { protected SysuiStatusBarStateController mStatusBarStateController; protected ShadeInteractor mShadeInteractor; + protected ActiveNotificationsInteractor mActiveNotificationsInteractor; + protected Handler mMainHandler; protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback; @@ -220,7 +224,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mKeyguardRepository, new FakeCommandQueue(), powerInteractor, - featureFlags, sceneContainerFlags, new FakeKeyguardBouncerRepository(), configurationRepository, @@ -290,6 +293,9 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { ) ); + mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(new ActiveNotificationListRepository()); + KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); @@ -362,6 +368,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mock(KeyguardFaceAuthInteractor.class), mShadeRepository, mShadeInteractor, + mActiveNotificationsInteractor, new JavaAdapter(mTestScope.getBackgroundScope()), mCastController, splitShadeStateController diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt index 61e4370949ca..65e0fa146fe3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt @@ -105,10 +105,7 @@ class ShadeInteractorImplTest : SysuiTestCase() { .create( test = this, featureFlags = - FakeFeatureFlagsClassicModule { - set(Flags.FACE_AUTH_REFACTOR, false) - set(Flags.FULL_SCREEN_USER_SWITCHER, true) - }, + FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, mocks = TestMocksModule( dozeParameters = dozeParameters, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt index 92eb6ed52c14..6e6e4384b7b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt @@ -86,10 +86,7 @@ class ShadeInteractorLegacyImplTest : SysuiTestCase() { .create( test = this, featureFlags = - FakeFeatureFlagsClassicModule { - set(Flags.FACE_AUTH_REFACTOR, false) - set(Flags.FULL_SCREEN_USER_SWITCHER, true) - }, + FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, mocks = TestMocksModule( dozeParameters = dozeParameters, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt index 729f3f840e1a..565e20a034db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt @@ -91,10 +91,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { .create( test = this, featureFlags = - FakeFeatureFlagsClassicModule { - set(Flags.FACE_AUTH_REFACTOR, false) - set(Flags.FULL_SCREEN_USER_SWITCHER, true) - }, + FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, mocks = TestMocksModule( dozeParameters = dozeParameters, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java index 63e46d1895bd..459040abd566 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; -import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; import static com.android.systemui.flags.Flags.KEYGUARD_TALKBACK_FIX; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON; @@ -56,7 +55,6 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.logging.KeyguardLogger; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FaceHelpMessageDeferral; @@ -72,6 +70,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.util.IndicationHelper; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; @@ -249,7 +248,6 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase { mFlags = new FakeFeatureFlags(); mFlags.set(KEYGUARD_TALKBACK_FIX, true); mFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); - mFlags.set(FACE_AUTH_REFACTOR, false); mController = new KeyguardIndicationController( mContext, mTestableLooper.getLooper(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index dd3ac927ebdf..aa53558e858d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -479,7 +479,7 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll createController(); // GIVEN face has already unlocked the device - when(mKeyguardUpdateMonitor.getUserUnlockedWithFace(anyInt())).thenReturn(true); + when(mKeyguardUpdateMonitor.isCurrentUserUnlockedWithFace()).thenReturn(true); String message = "A message"; mController.setVisible(true); @@ -586,7 +586,7 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll createController(); String message = mContext.getString(R.string.keyguard_retry); when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true); - when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true); + when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(true); when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(false); mController.setVisible(true); @@ -602,7 +602,7 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll String message = mContext.getString(R.string.keyguard_retry); when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true); when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true); - when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true); + when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(true); mController.setVisible(true); mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 8fa7cd291851..7546dfa1cbf9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -66,8 +66,8 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -93,88 +93,98 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { whenever(interactionJankMonitor.end(anyInt())).thenReturn(true) uiEventLogger = UiEventLoggerFake() - controller = object : StatusBarStateControllerImpl( - uiEventLogger, - interactionJankMonitor, - mock(), - { shadeInteractor } - ) { - override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator } - } - - val powerInteractor = PowerInteractor( - FakePowerRepository(), - FalsingCollectorFake(), - mock(), - controller) + controller = + object : + StatusBarStateControllerImpl( + uiEventLogger, + interactionJankMonitor, + mock(), + { shadeInteractor } + ) { + override fun createDarkAnimator(): ObjectAnimator { + return mockDarkAnimator + } + } + + val powerInteractor = + PowerInteractor(FakePowerRepository(), FalsingCollectorFake(), mock(), controller) val keyguardRepository = FakeKeyguardRepository() val keyguardTransitionRepository = FakeKeyguardTransitionRepository() val featureFlags = FakeFeatureFlagsClassic() val shadeRepository = FakeShadeRepository() val sceneContainerFlags = FakeSceneContainerFlags() val configurationRepository = FakeConfigurationRepository() - val keyguardInteractor = KeyguardInteractor( - keyguardRepository, - FakeCommandQueue(), - powerInteractor, - featureFlags, - sceneContainerFlags, - FakeKeyguardBouncerRepository(), - configurationRepository, - shadeRepository, - utils::sceneInteractor) - val keyguardTransitionInteractor = KeyguardTransitionInteractor( - testScope.backgroundScope, - keyguardTransitionRepository, - { keyguardInteractor }, - { fromLockscreenTransitionInteractor }, - { fromPrimaryBouncerTransitionInteractor }) - fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( - keyguardTransitionRepository, - keyguardTransitionInteractor, - testScope.backgroundScope, - keyguardInteractor, - featureFlags, - shadeRepository, - powerInteractor, - { - InWindowLauncherUnlockAnimationInteractor( - InWindowLauncherUnlockAnimationRepository(), - testScope, - keyguardTransitionInteractor, - { FakeKeyguardSurfaceBehindRepository() }, - mock(), - ) - }) - fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( - keyguardTransitionRepository, - keyguardTransitionInteractor, - testScope.backgroundScope, - keyguardInteractor, - featureFlags, - mock(), - mock(), - powerInteractor) - shadeInteractor = ShadeInteractorImpl( - testScope.backgroundScope, - FakeDeviceProvisioningRepository(), - FakeDisableFlagsRepository(), - mock(), - keyguardRepository, - keyguardTransitionInteractor, - powerInteractor, - FakeUserSetupRepository(), - mock(), - ShadeInteractorLegacyImpl( - testScope.backgroundScope, + val keyguardInteractor = + KeyguardInteractor( keyguardRepository, - SharedNotificationContainerInteractor( - configurationRepository, - mContext, - ResourcesSplitShadeStateController()), + FakeCommandQueue(), + powerInteractor, + sceneContainerFlags, + FakeKeyguardBouncerRepository(), + configurationRepository, shadeRepository, + utils::sceneInteractor + ) + val keyguardTransitionInteractor = + KeyguardTransitionInteractor( + testScope.backgroundScope, + keyguardTransitionRepository, + { keyguardInteractor }, + { fromLockscreenTransitionInteractor }, + { fromPrimaryBouncerTransitionInteractor } + ) + fromLockscreenTransitionInteractor = + FromLockscreenTransitionInteractor( + keyguardTransitionRepository, + keyguardTransitionInteractor, + testScope.backgroundScope, + keyguardInteractor, + featureFlags, + shadeRepository, + powerInteractor, + { + InWindowLauncherUnlockAnimationInteractor( + InWindowLauncherUnlockAnimationRepository(), + testScope, + keyguardTransitionInteractor, + { FakeKeyguardSurfaceBehindRepository() }, + mock(), + ) + } + ) + fromPrimaryBouncerTransitionInteractor = + FromPrimaryBouncerTransitionInteractor( + keyguardTransitionRepository, + keyguardTransitionInteractor, + testScope.backgroundScope, + keyguardInteractor, + featureFlags, + mock(), + mock(), + powerInteractor + ) + shadeInteractor = + ShadeInteractorImpl( + testScope.backgroundScope, + FakeDeviceProvisioningRepository(), + FakeDisableFlagsRepository(), + mock(), + keyguardRepository, + keyguardTransitionInteractor, + powerInteractor, + FakeUserSetupRepository(), + mock(), + ShadeInteractorLegacyImpl( + testScope.backgroundScope, + keyguardRepository, + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ResourcesSplitShadeStateController() + ), + shadeRepository, + ) ) - ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt index d1a46fca21c8..057dcb2a156e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt @@ -62,7 +62,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { private val ongoingCallRepository = OngoingCallRepository() private val underTest = - StatusBarModeRepositoryImpl( + StatusBarModePerDisplayRepositoryImpl( testScope.backgroundScope, DISPLAY_ID, commandQueue, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index 428574bb15f8..fa5fad06b671 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfte import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING @@ -57,6 +58,7 @@ class StackCoordinatorTest : SysuiTestCase() { @Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor + @Mock private lateinit var activeNotificationsInteractor: ActiveNotificationsInteractor @Mock private lateinit var stackController: NotifStackController @Mock private lateinit var section: NotifSection @@ -75,6 +77,7 @@ class StackCoordinatorTest : SysuiTestCase() { groupExpansionManagerImpl, notificationIconAreaController, renderListInteractor, + activeNotificationsInteractor, ) coordinator.attach(pipeline) afterRenderListListener = withArgCaptor { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt new file mode 100644 index 000000000000..4ab3cd49b297 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.domain.interactor + +import androidx.test.filters.SmallTest +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.collection.render.NotifStats +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import org.junit.Test + +@SmallTest +class ActiveNotificationsInteractorTest : SysuiTestCase() { + + @Component(modules = [SysUITestModule::class]) + @SysUISingleton + interface TestComponent : SysUITestComponent<ActiveNotificationsInteractor> { + val activeNotificationListRepository: ActiveNotificationListRepository + + @Component.Factory + interface Factory { + fun create(@BindsInstance test: SysuiTestCase): TestComponent + } + } + + private val testComponent: TestComponent = + DaggerActiveNotificationsInteractorTest_TestComponent.factory().create(test = this) + + @Test + fun testAreAnyNotificationsPresent_isTrue() = + testComponent.runTest { + val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent) + + activeNotificationListRepository.setActiveNotifs(2) + runCurrent() + + assertThat(areAnyNotificationsPresent).isTrue() + assertThat(underTest.areAnyNotificationsPresentValue).isTrue() + } + + @Test + fun testAreAnyNotificationsPresent_isFalse() = + testComponent.runTest { + val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent) + + activeNotificationListRepository.setActiveNotifs(0) + runCurrent() + + assertThat(areAnyNotificationsPresent).isFalse() + assertThat(underTest.areAnyNotificationsPresentValue).isFalse() + } + + @Test + fun testHasClearableNotifications_whenHasClearableAlertingNotifs() = + testComponent.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasClearable).isTrue() + } + + @Test + fun testHasClearableNotifications_whenHasClearableSilentNotifs() = + testComponent.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + assertThat(hasClearable).isTrue() + } + + @Test + fun testHasClearableNotifications_whenHasNoClearableNotifs() = + testComponent.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasClearable).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java index a64ac674a91c..22c5bae93489 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java @@ -114,9 +114,46 @@ public class FooterViewTest extends SysuiTestCase { } @Test + public void testSetClearAllButtonText_resourceOnlyFetchedOnce() { + int resId = R.string.clear_all_notifications_text; + mView.setClearAllButtonText(resId); + verify(mSpyContext).getString(eq(resId)); + + clearInvocations(mSpyContext); + + assertThat(((TextView) mView.findViewById(R.id.dismiss_text)) + .getText().toString()).contains("Clear all"); + + // Set it a few more times, it shouldn't lead to the resource being fetched again + mView.setClearAllButtonText(resId); + mView.setClearAllButtonText(resId); + + verify(mSpyContext, never()).getString(anyInt()); + } + + @Test + public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() { + int resId = R.string.accessibility_clear_all; + mView.setClearAllButtonDescription(resId); + verify(mSpyContext).getString(eq(resId)); + + clearInvocations(mSpyContext); + + assertThat(((TextView) mView.findViewById(R.id.dismiss_text)) + .getContentDescription().toString()).contains("Clear all notifications"); + + // Set it a few more times, it shouldn't lead to the resource being fetched again + mView.setClearAllButtonDescription(resId); + mView.setClearAllButtonDescription(resId); + + verify(mSpyContext, never()).getString(anyInt()); + } + + @Test public void testSetMessageString_resourceOnlyFetchedOnce() { - mView.setMessageString(R.string.unlock_to_see_notif_text); - verify(mSpyContext).getString(eq(R.string.unlock_to_see_notif_text)); + int resId = R.string.unlock_to_see_notif_text; + mView.setMessageString(resId); + verify(mSpyContext).getString(eq(resId)); clearInvocations(mSpyContext); @@ -124,22 +161,23 @@ public class FooterViewTest extends SysuiTestCase { .getText().toString()).contains("Unlock"); // Set it a few more times, it shouldn't lead to the resource being fetched again - mView.setMessageString(R.string.unlock_to_see_notif_text); - mView.setMessageString(R.string.unlock_to_see_notif_text); + mView.setMessageString(resId); + mView.setMessageString(resId); verify(mSpyContext, never()).getString(anyInt()); } @Test public void testSetMessageIcon_resourceOnlyFetchedOnce() { - mView.setMessageIcon(R.drawable.ic_friction_lock_closed); - verify(mSpyContext).getDrawable(eq(R.drawable.ic_friction_lock_closed)); + int resId = R.drawable.ic_friction_lock_closed; + mView.setMessageIcon(resId); + verify(mSpyContext).getDrawable(eq(resId)); clearInvocations(mSpyContext); // Set it a few more times, it shouldn't lead to the resource being fetched again - mView.setMessageIcon(R.drawable.ic_friction_lock_closed); - mView.setMessageIcon(R.drawable.ic_friction_lock_closed); + mView.setMessageIcon(resId); + mView.setMessageIcon(resId); verify(mSpyContext, never()).getDrawable(anyInt()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt index 57a7c3c7e2bf..94dcf7a18514 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt @@ -18,37 +18,222 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.systemui.Flags +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.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 +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.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.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule +import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.ui.isAnimating +import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest +import dagger.BindsInstance +import dagger.Component +import java.util.Optional +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest class FooterViewModelTest : SysuiTestCase() { - private val repository = ActiveNotificationListRepository() - private val interactor = SeenNotificationsInteractor(repository) - private val underTest = FooterViewModel(interactor) + private lateinit var footerViewModel: FooterViewModel - @Test - fun testMessageVisible_whenFilteredNotifications() = runTest { - val message by collectLastValue(underTest.message) + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + ActivatableNotificationViewModelModule::class, + FooterViewModelModule::class, + HeadlessSystemUserModeModule::class, + ] + ) + interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> { + val activeNotificationListRepository: ActiveNotificationListRepository + val configurationRepository: FakeConfigurationRepository + val keyguardRepository: FakeKeyguardRepository + val keyguardTransitionRepository: FakeKeyguardTransitionRepository + val shadeRepository: FakeShadeRepository + val powerRepository: FakePowerRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private val dozeParameters: DozeParameters = mock() - repository.hasFilteredOutSeenNotifications.value = true + private val testComponent: TestComponent = + DaggerFooterViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = + TestMocksModule( + dozeParameters = dozeParameters, + ) + ) - assertThat(message?.visible).isTrue() + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR) + + // The underTest in the component is Optional, because that matches the provider we + // currently have for the footer view model. + footerViewModel = testComponent.underTest.get() } @Test - fun testMessageVisible_whenNoFilteredNotifications() = runTest { - val message by collectLastValue(underTest.message) + fun testMessageVisible_whenFilteredNotifications() = + testComponent.runTest { + val message by collectLastValue(footerViewModel.message) - repository.hasFilteredOutSeenNotifications.value = false + activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true - assertThat(message?.visible).isFalse() - } + assertThat(message?.visible).isTrue() + } + + @Test + fun testMessageVisible_whenNoFilteredNotifications() = + testComponent.runTest { + val message by collectLastValue(footerViewModel.message) + + activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false + + assertThat(message?.visible).isFalse() + } + + @Test + fun testClearAllButtonVisible_whenHasClearableNotifs() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + assertThat(button?.isVisible?.value).isTrue() + } + + @Test + fun testClearAllButtonVisible_whenHasNoClearableNotifs() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(button?.isVisible?.value).isFalse() + } + + @Test + fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + runCurrent() + + // WHEN shade is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setLegacyShadeExpansion(1f) + // AND QS not expanded + shadeRepository.setQsExpansion(0f) + // AND device is awake + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + runCurrent() + + // AND there are clearable notifications + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + // THEN button visibility should animate + assertThat(button?.isVisible?.isAnimating).isTrue() + } + + @Test + fun testClearAllButtonAnimating_whenShadeNotExpanded() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + runCurrent() + + // WHEN shade is collapsed + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setLegacyShadeExpansion(0f) + // AND QS not expanded + shadeRepository.setQsExpansion(0f) + // AND device is awake + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + runCurrent() + + // AND there are clearable notifications + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + // THEN button visibility should not animate + assertThat(button?.isVisible?.isAnimating).isFalse() + } } 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 034103598bb0..360a373711d6 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 @@ -26,10 +26,10 @@ 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.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository -import com.android.systemui.statusbar.notification.shared.activeNotificationModel import com.android.systemui.statusbar.notification.shared.byIsAmbient import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply import com.android.systemui.statusbar.notification.shared.byIsPulsing 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 10d4c627bc4a..b3fc25c47912 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 @@ -93,7 +93,6 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() { test = this, featureFlags = FakeFeatureFlagsClassicModule { - setDefault(Flags.FACE_AUTH_REFACTOR) set(Flags.FULL_SCREEN_USER_SWITCHER, value = false) }, mocks = 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 c2a1519f85dd..741564505b53 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 @@ -43,10 +43,10 @@ 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.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository -import com.android.systemui.statusbar.notification.shared.activeNotificationModel import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository @@ -105,7 +105,6 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { test = this, featureFlags = FakeFeatureFlagsClassicModule { - setDefault(Flags.FACE_AUTH_REFACTOR) set(Flags.FULL_SCREEN_USER_SWITCHER, value = false) }, mocks = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 3e331a6cff2a..0a9bac91004a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -999,11 +999,25 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse(); } + @Test + public void shouldNotBubbleUp_suspended() { + assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createSuspendedBubble())) + .isFalse(); + } + + private NotificationEntry createSuspendedBubble() { + return createBubble(null, null, true); + } + private NotificationEntry createBubble() { - return createBubble(null, null); + return createBubble(null, null, false); } private NotificationEntry createBubble(String groupKey, Integer groupAlert) { + return createBubble(groupKey, groupAlert, false); + } + + private NotificationEntry createBubble(String groupKey, Integer groupAlert, Boolean suspended) { Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder( PendingIntent.getActivity(mContext, 0, new Intent().setPackage(mContext.getPackageName()), @@ -1031,6 +1045,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .setNotification(n) .setImportance(IMPORTANCE_HIGH) .setCanBubble(true) + .setSuspended(suspended) .build(); } 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 1c7fd565c289..7361f6baa7b6 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 @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.interruption import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE @@ -37,7 +36,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() { init { - setFlagsRule.disableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR) + mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME) } override val provider by lazy { 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 df6f0d716577..d2c046c67fb0 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 @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.interruption import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK @@ -30,7 +29,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() { init { - setFlagsRule.enableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR) + mSetFlagsRule.enableFlags(VisualInterruptionRefactor.FLAG_NAME) } override val provider by lazy { 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 a3b7e8c8ca0a..2ac0cb7499d2 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 @@ -44,7 +44,6 @@ import android.graphics.drawable.Icon import android.hardware.display.FakeAmbientDisplayConfiguration import android.os.Looper import android.os.PowerManager -import android.platform.test.flag.junit.SetFlagsRule import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED import android.provider.Settings.Global.HEADS_UP_OFF import android.provider.Settings.Global.HEADS_UP_ON @@ -84,15 +83,10 @@ import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.Assert.assertEquals import org.junit.Before -import org.junit.Rule import org.junit.Test import org.mockito.Mockito.`when` as whenever abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { - @JvmField - @Rule - val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) - private val fakeLogBuffer = LogBuffer( name = "FakeLog", @@ -601,6 +595,13 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test + fun testShouldNotBubble_bubbleAppSuspended() { + ensureBubbleState() + assertShouldNotBubble(buildBubbleEntry { packageSuspended = true }) + assertNoEventsLogged() + } + + @Test fun testShouldNotFsi_noFullScreenIntent() { forEachFsiState { assertShouldNotFsi(buildFsiEntry { hasFsi = false }) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt index ca105f3e52ea..16c5c8a98253 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt @@ -15,7 +15,6 @@ package com.android.systemui.statusbar.notification.shared -import android.graphics.drawable.Icon import com.google.common.truth.Correspondence val byKey: Correspondence<ActiveNotificationModel, String> = @@ -38,30 +37,3 @@ val byIsLastMessageFromReply: Correspondence<ActiveNotificationModel, Boolean> = ) val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> = Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of") - -fun activeNotificationModel( - key: String, - groupKey: String? = null, - isAmbient: Boolean = false, - isRowDismissed: Boolean = false, - isSilent: Boolean = false, - isLastMessageFromReply: Boolean = false, - isSuppressedFromStatusBar: Boolean = false, - isPulsing: Boolean = false, - aodIcon: Icon? = null, - shelfIcon: Icon? = null, - statusBarIcon: Icon? = null, -) = - ActiveNotificationModel( - key = key, - groupKey = groupKey, - isAmbient = isAmbient, - isRowDismissed = isRowDismissed, - isSilent = isSilent, - isLastMessageFromReply = isLastMessageFromReply, - isSuppressedFromStatusBar = isSuppressedFromStatusBar, - isPulsing = isPulsing, - aodIcon = aodIcon, - shelfIcon = shelfIcon, - statusBarIcon = statusBarIcon, - ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 590389035e92..ff5c02622e4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -36,7 +36,6 @@ import static org.mockito.Mockito.when; import static kotlinx.coroutines.flow.FlowKt.emptyFlow; -import android.content.res.Resources; import android.metrics.LogMaker; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -53,7 +52,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; -import com.android.systemui.common.ui.ConfigurationState; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; @@ -74,7 +72,6 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; @@ -84,17 +81,15 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -170,8 +165,14 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; + private final ActiveNotificationListRepository mActiveNotificationsRepository = + new ActiveNotificationListRepository(); + + private final ActiveNotificationsInteractor mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(mActiveNotificationsRepository); + private final SeenNotificationsInteractor mSeenNotificationsInteractor = - new SeenNotificationsInteractor(new ActiveNotificationListRepository()); + new SeenNotificationsInteractor(mActiveNotificationsRepository); private NotificationStackScrollLayoutController mController; @@ -701,6 +702,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mUiEventLogger, mRemoteInputManager, mVisibilityLocationProviderDelegator, + mActiveNotificationsInteractor, mSeenNotificationsInteractor, mViewBinder, mShadeController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt index 46e8453a6d1d..ac20683b4f49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt @@ -34,9 +34,11 @@ import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.unfold.TestUnfoldTransitionProvider import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl -import com.android.systemui.util.animation.FakeAnimationStatusRepository +import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepository import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import java.time.Duration +import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.test.TestScope @@ -47,8 +49,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoAnnotations -import java.time.Duration -import java.util.Optional @OptIn(ExperimentalCoroutinesApi::class) @SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt new file mode 100644 index 000000000000..f00abc9c3f63 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.notification.stack.ui.viewmodel + +import android.app.NotificationManager.Policy +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +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.domain.CommonDomainLayerModule +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.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.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule +import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule +import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository +import com.android.systemui.unfold.UnfoldTransitionModule +import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationListViewModelTest : SysuiTestCase() { + + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + ActivatableNotificationViewModelModule::class, + CommonDomainLayerModule::class, + FooterViewModelModule::class, + HeadlessSystemUserModeModule::class, + UnfoldTransitionModule.Bindings::class, + ] + ) + interface TestComponent : SysUITestComponent<NotificationListViewModel> { + val activeNotificationListRepository: ActiveNotificationListRepository + val keyguardTransitionRepository: FakeKeyguardTransitionRepository + val shadeRepository: FakeShadeRepository + val zenModeRepository: FakeZenModeRepository + val configurationController: FakeConfigurationController + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private val testComponent: TestComponent = + DaggerNotificationListViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule() + ) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR) + } + + @Test + fun testIsImportantForAccessibility_falseWhenNoNotifs() = + testComponent.runTest { + val important by collectLastValue(underTest.isImportantForAccessibility) + + // WHEN on lockscreen + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + // AND has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + testScope.runCurrent() + + // THEN not important + assertThat(important).isFalse() + } + + @Test + fun testIsImportantForAccessibility_trueWhenNotifs() = + testComponent.runTest { + val important by collectLastValue(underTest.isImportantForAccessibility) + + // WHEN on lockscreen + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + // AND has notifs + activeNotificationListRepository.setActiveNotifs(count = 2) + runCurrent() + + // THEN is important + assertThat(important).isTrue() + } + + @Test + fun testIsImportantForAccessibility_trueWhenNotKeyguard() = + testComponent.runTest { + val important by collectLastValue(underTest.isImportantForAccessibility) + + // WHEN not on lockscreen + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope, + ) + // AND has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + runCurrent() + + // THEN is still important + assertThat(important).isTrue() + } + + @Test + fun testShouldShowEmptyShadeView_trueWhenNoNotifs() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + runCurrent() + + // THEN should show + assertThat(shouldShow).isTrue() + } + + @Test + fun testShouldShowEmptyShadeView_falseWhenNotifs() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has notifs + activeNotificationListRepository.setActiveNotifs(count = 2) + runCurrent() + + // THEN should not show + assertThat(shouldShow).isFalse() + } + + @Test + fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + // AND quick settings are expanded + shadeRepository.legacyQsFullscreen.value = true + runCurrent() + + // THEN should not show + assertThat(shouldShow).isFalse() + } + + @Test + fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + // AND quick settings are expanded + shadeRepository.setQsExpansion(1f) + // AND split shade is enabled + overrideResource(R.bool.config_use_split_notification_shade, true) + configurationController.notifyConfigurationChanged() + runCurrent() + + // THEN should show + assertThat(shouldShow).isTrue() + } + + @Test + fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + // AND transitioning to AOD + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0f, + ) + ) + runCurrent() + + // THEN should not show + assertThat(shouldShow).isFalse() + } + + @Test + fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + // AND is on bouncer + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + testScope, + ) + runCurrent() + + // THEN should not show + assertThat(shouldShow).isFalse() + } + + @Test + fun testAreNotificationsHiddenInShade_true() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + zenModeRepository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + runCurrent() + + assertThat(hidden).isTrue() + } + + @Test + fun testAreNotificationsHiddenInShade_false() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + zenModeRepository.zenMode.value = Settings.Global.ZEN_MODE_OFF + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun testHasFilteredOutSeenNotifications_true() = + testComponent.runTest { + val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications) + + activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true + runCurrent() + + assertThat(hasFilteredNotifs).isTrue() + } + + @Test + fun testHasFilteredOutSeenNotifications_false() = + testComponent.runTest { + val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications) + + activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false + runCurrent() + + assertThat(hasFilteredNotifs).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index e61b4f81aaee..051a4c1c05cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -135,7 +135,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mKeyguardStateController.isShowing()).thenReturn(true); when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true); - when(mKeyguardStateController.isFaceEnrolled()).thenReturn(true); + when(mKeyguardStateController.isFaceEnrolledAndEnabled()).thenReturn(true); when(mKeyguardStateController.isUnlocked()).thenReturn(false); when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean())) .thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 4b1c7e8faa38..4422764c4bac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -157,6 +157,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -347,6 +348,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true); when(mDozeParameters.getAlwaysOn()).thenReturn(true); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); + // TODO: b/312476335 - Update to check flag and instantiate old or new implementation. + mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME); IThermalService thermalService = mock(IThermalService.class); mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt index bd0dbeebd752..91cbc3274900 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt @@ -86,7 +86,7 @@ class KeyguardBypassControllerTest : SysuiTestCase() { featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true) - whenever(keyguardStateController.isFaceEnrolled).thenReturn(true) + whenever(keyguardStateController.isFaceEnrolledAndEnabled).thenReturn(true) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 361df1c63ffd..62a2bc54af20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -60,7 +60,6 @@ import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.res.R; import com.android.systemui.scene.SceneTestUtils; @@ -166,7 +165,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mKeyguardRepository, mCommandQueue, PowerInteractorFactory.create().getPowerInteractor(), - mFeatureFlags, mSceneTestUtils.getSceneContainerFlags(), new FakeKeyguardBouncerRepository(), new FakeConfigurationRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java index 287ebba4db24..bde2243db4e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java @@ -54,7 +54,7 @@ import java.util.Objects; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper -public class LightsOutNotifControllerTest extends SysuiTestCase { +public class LegacyLightsOutNotifControllerTest extends SysuiTestCase { private static final int LIGHTS_ON = 0; private static final int LIGHTS_OUT = APPEARANCE_LOW_PROFILE_BARS; @@ -68,7 +68,7 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksCaptor; private View mLightsOutView; - private LightsOutNotifController mLightsOutNotifController; + private LegacyLightsOutNotifController mLightsOutNotifController; private int mDisplayId; private Observer<Boolean> mHaActiveNotifsObserver; private CommandQueue.Callbacks mCallbacks; @@ -83,7 +83,7 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { when(mNotifLiveDataStore.getHasActiveNotifs()).thenReturn(mHasActiveNotifs); when(mHasActiveNotifs.getValue()).thenReturn(false); - mLightsOutNotifController = new LightsOutNotifController( + mLightsOutNotifController = new LegacyLightsOutNotifController( mLightsOutView, mWindowManager, mNotifLiveDataStore, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index c45ecf3fe225..f6419a9c4784 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -121,7 +121,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds) ); - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -142,7 +142,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(0 /* appearance */, secondBounds) ); - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -165,7 +165,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds) ); - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -190,7 +190,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds) ); - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -214,7 +214,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(0 /* appearance */, secondBounds) ); - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -231,7 +231,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1)) ); - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -249,7 +249,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(0, new Rect(0, 0, 1, 1)) ); - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -266,7 +266,7 @@ public class LightBarControllerTest extends SysuiTestCase { new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1)) ); - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, STATUS_BAR_BOUNDS, @@ -276,7 +276,7 @@ public class LightBarControllerTest extends SysuiTestCase { reset(mStatusBarIconController); // WHEN the same appearance regions but different status bar mode is sent - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.LIGHTS_OUT_TRANSPARENT, STATUS_BAR_BOUNDS, @@ -298,7 +298,7 @@ public class LightBarControllerTest extends SysuiTestCase { /* start= */ new Rect(0, 0, 10, 10), /* end= */ new Rect(0, 0, 20, 20)); - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, startingBounds, @@ -311,7 +311,7 @@ public class LightBarControllerTest extends SysuiTestCase { BoundsPair newBounds = new BoundsPair( /* start= */ new Rect(0, 0, 30, 30), /* end= */ new Rect(0, 0, 40, 40)); - mStatusBarModeRepository.getStatusBarAppearance().setValue( + mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue( new StatusBarAppearance( StatusBarMode.TRANSPARENT, newBounds, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt new file mode 100644 index 000000000000..5a0e13d02b92 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.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.statusbar.phone.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.data.model.StatusBarMode +import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository +import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@SmallTest +class LightsOutInteractorTest : SysuiTestCase() { + + private val statusBarModeRepository = FakeStatusBarModeRepository() + private val interactor: LightsOutInteractor = LightsOutInteractor(statusBarModeRepository) + + @Test + fun isLowProfile_lightsOutStatusBarMode_false() = runTest { + statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.LIGHTS_OUT + + val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID)) + + assertThat(actual).isTrue() + } + + @Test + fun isLowProfile_lightsOutTransparentStatusBarMode_true() = runTest { + statusBarModeRepository.defaultDisplay.statusBarMode.value = + StatusBarMode.LIGHTS_OUT_TRANSPARENT + + val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID)) + + assertThat(actual).isTrue() + } + + @Test + fun isLowProfile_transparentStatusBarMode_false() = runTest { + statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT + + val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID)) + + assertThat(actual).isFalse() + } +} 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 0b87fe8da184..17c29382b39a 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 @@ -46,7 +46,6 @@ import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.common.ui.ConfigurationState; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.LogcatEchoTracker; import com.android.systemui.plugins.DarkIconDispatcher; @@ -695,7 +694,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mLocationPublisher, mMockNotificationAreaController, mShadeExpansionStateManager, - mock(FeatureFlagsClassic.class), mStatusBarIconController, mIconManagerFactory, mCollapsedStatusBarViewModel, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index 49de5125cb4d..7b73528cd932 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -549,7 +549,7 @@ class OngoingCallControllerTest : SysuiTestCase() { @Test fun fullscreenIsTrue_chipStillClickable() { notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - statusBarModeRepository.isInFullscreenMode.value = true + statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true testScope.runCurrent() assertThat(chipView.hasOnClickListeners()).isTrue() @@ -559,7 +559,7 @@ class OngoingCallControllerTest : SysuiTestCase() { @Test fun callStartedInImmersiveMode_swipeGestureCallbackAdded() { - statusBarModeRepository.isInFullscreenMode.value = true + statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true testScope.runCurrent() notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) @@ -570,7 +570,7 @@ class OngoingCallControllerTest : SysuiTestCase() { @Test fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() { - statusBarModeRepository.isInFullscreenMode.value = false + statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false testScope.runCurrent() notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) @@ -583,7 +583,7 @@ class OngoingCallControllerTest : SysuiTestCase() { fun transitionToImmersiveMode_swipeGestureCallbackAdded() { notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - statusBarModeRepository.isInFullscreenMode.value = true + statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true testScope.runCurrent() verify(mockSwipeStatusBarAwayGestureHandler) @@ -592,11 +592,11 @@ class OngoingCallControllerTest : SysuiTestCase() { @Test fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() { - statusBarModeRepository.isInFullscreenMode.value = true + statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) reset(mockSwipeStatusBarAwayGestureHandler) - statusBarModeRepository.isInFullscreenMode.value = false + statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false testScope.runCurrent() verify(mockSwipeStatusBarAwayGestureHandler) @@ -605,7 +605,7 @@ class OngoingCallControllerTest : SysuiTestCase() { @Test fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() { - statusBarModeRepository.isInFullscreenMode.value = true + statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true testScope.runCurrent() val ongoingCallNotifEntry = createOngoingCallNotifEntry() notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 688f739f61f8..09dc1e537e9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -17,49 +17,77 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import androidx.test.filters.SmallTest +import com.android.systemui.CoroutineTestScopeModule +import com.android.systemui.Flags +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectValues +import com.android.systemui.collectLastValue +import com.android.systemui.collectValues +import com.android.systemui.dagger.SysUISingleton 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.android.systemui.runTest +import com.android.systemui.statusbar.data.model.StatusBarMode +import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository +import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { - private lateinit var underTest: CollapsedStatusBarViewModel + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + ] + ) + interface TestComponent : SysUITestComponent<CollapsedStatusBarViewModelImpl> { + val statusBarModeRepository: FakeStatusBarModeRepository + val activeNotificationListRepository: ActiveNotificationListRepository + val keyguardTransitionRepository: FakeKeyguardTransitionRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + testScope: CoroutineTestScopeModule, + ): TestComponent + } + } - private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository - private lateinit var testScope: TestScope + @OptIn(ExperimentalCoroutinesApi::class) + private val testComponent: TestComponent = + DaggerCollapsedStatusBarViewModelImplTest_TestComponent.factory() + .create( + test = this, + testScope = CoroutineTestScopeModule(TestScope(UnconfinedTestDispatcher())), + ) @Before fun setUp() { - testScope = TestScope(UnconfinedTestDispatcher()) - - keyguardTransitionRepository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - repository = keyguardTransitionRepository, - ) - .keyguardTransitionInteractor - underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope) + mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR) } @Test fun isTransitioningFromLockscreenToOccluded_started_isTrue() = - testScope.runTest { - val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + testComponent.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -77,8 +105,8 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { @Test fun isTransitioningFromLockscreenToOccluded_running_isTrue() = - testScope.runTest { - val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + testComponent.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -96,13 +124,13 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { @Test fun isTransitioningFromLockscreenToOccluded_finished_isFalse() = - testScope.runTest { - val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + testComponent.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope) keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.OCCLUDED, - this.testScheduler, + testScope.testScheduler, ) assertThat(underTest.isTransitioningFromLockscreenToOccluded.value).isFalse() @@ -112,8 +140,8 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { @Test fun isTransitioningFromLockscreenToOccluded_canceled_isFalse() = - testScope.runTest { - val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + testComponent.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -131,8 +159,8 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { @Test fun isTransitioningFromLockscreenToOccluded_irrelevantTransition_isFalse() = - testScope.runTest { - val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + testComponent.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -150,8 +178,8 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { @Test fun isTransitioningFromLockscreenToOccluded_followsRepoUpdates() = - testScope.runTest { - val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + testComponent.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -182,7 +210,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { @Test fun transitionFromLockscreenToDreamStartedEvent_started_emitted() = - testScope.runTest { + testComponent.runTest { val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent) keyguardTransitionRepository.sendTransitionStep( @@ -199,7 +227,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { @Test fun transitionFromLockscreenToDreamStartedEvent_startedMultiple_emittedMultiple() = - testScope.runTest { + testComponent.runTest { val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent) keyguardTransitionRepository.sendTransitionStep( @@ -234,7 +262,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { @Test fun transitionFromLockscreenToDreamStartedEvent_startedThenRunning_emittedOnlyOne() = - testScope.runTest { + testComponent.runTest { val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent) keyguardTransitionRepository.sendTransitionStep( @@ -283,7 +311,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { @Test fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransition_notEmitted() = - testScope.runTest { + testComponent.runTest { val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent) keyguardTransitionRepository.sendTransitionStep( @@ -300,7 +328,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { @Test fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransitionState_notEmitted() = - testScope.runTest { + testComponent.runTest { val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent) keyguardTransitionRepository.sendTransitionStep( @@ -317,4 +345,65 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { assertThat(emissions).isEmpty() } + + @Test + fun areNotificationsLightsOut_lowProfileWithNotifications_true() = + testComponent.runTest { + statusBarModeRepository.defaultDisplay.statusBarMode.value = + StatusBarMode.LIGHTS_OUT_TRANSPARENT + activeNotificationListRepository.activeNotifications.value = + activeNotificationsStore(testNotifications) + + val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID)) + + assertThat(actual).isTrue() + } + + @Test + fun areNotificationsLightsOut_lowProfileWithoutNotifications_false() = + testComponent.runTest { + statusBarModeRepository.defaultDisplay.statusBarMode.value = + StatusBarMode.LIGHTS_OUT_TRANSPARENT + activeNotificationListRepository.activeNotifications.value = + activeNotificationsStore(emptyList()) + + val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID)) + + assertThat(actual).isFalse() + } + + @Test + fun areNotificationsLightsOut_defaultStatusBarModeWithoutNotifications_false() = + testComponent.runTest { + statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT + activeNotificationListRepository.activeNotifications.value = + activeNotificationsStore(emptyList()) + + val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID)) + + assertThat(actual).isFalse() + } + + @Test + fun areNotificationsLightsOut_defaultStatusBarModeWithNotifications_false() = + testComponent.runTest { + statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT + activeNotificationListRepository.activeNotifications.value = + activeNotificationsStore(testNotifications) + + val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID)) + + assertThat(actual).isFalse() + } + + private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) = + ActiveNotificationsStore.Builder() + .apply { notifications.forEach(::addIndividualNotif) } + .build() + + private val testNotifications = + listOf( + activeNotificationModel(key = "notif1"), + activeNotificationModel(key = "notif2"), + ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt index 88587b2db0f9..bc50f7967403 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt @@ -16,11 +16,20 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel { + private val areNotificationLightsOut = MutableStateFlow(false) + override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false) override val transitionFromLockscreenToDreamStartedEvent = MutableSharedFlow<Unit>() + + override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut + + fun setNotificationLightsOut(lightsOut: Boolean) { + areNotificationLightsOut.value = lightsOut + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java index 5c960b6633db..01dad381efa0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java @@ -100,16 +100,16 @@ public class KeyguardStateControllerTest extends SysuiTestCase { public void testFaceAuthEnrolleddChanged_calledWhenFaceEnrollmentStateChanges() { KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class); - when(mKeyguardUpdateMonitor.isFaceEnrolled(anyInt())).thenReturn(false); + when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(false); verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture()); mKeyguardStateController.addCallback(callback); - assertThat(mKeyguardStateController.isFaceEnrolled()).isFalse(); + assertThat(mKeyguardStateController.isFaceEnrolledAndEnabled()).isFalse(); - when(mKeyguardUpdateMonitor.isFaceEnrolled(anyInt())).thenReturn(true); + when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(true); mUpdateCallbackCaptor.getValue().onBiometricEnrollmentStateChanged( BiometricSourceType.FACE); - assertThat(mKeyguardStateController.isFaceEnrolled()).isTrue(); + assertThat(mKeyguardStateController.isFaceEnrolledAndEnabled()).isTrue(); verify(callback).onFaceEnrolledChanged(); } 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 index 99e62eec890c..dbb106264ecd 100644 --- 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 @@ -128,8 +128,7 @@ class ZenModeInteractorTest : SysuiTestCase() { testComponent.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - repository.consolidatedNotificationPolicy.value = - policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) repository.zenMode.value = Settings.Global.ZEN_MODE_OFF runCurrent() @@ -141,8 +140,7 @@ class ZenModeInteractorTest : SysuiTestCase() { testComponent.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - repository.consolidatedNotificationPolicy.value = - policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR) + repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR) repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS runCurrent() @@ -154,19 +152,10 @@ class ZenModeInteractorTest : SysuiTestCase() { testComponent.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - repository.consolidatedNotificationPolicy.value = - policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + repository.setSuppressedVisualEffects(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/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index 96db09edaf88..59bf9f30a828 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -21,7 +21,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.StatusBarState @@ -55,7 +54,6 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() { keyguardRepository, mock<CommandQueue>(), PowerInteractorFactory.create().powerInteractor, - FakeFeatureFlagsClassic(), sceneTestUtils.sceneContainerFlags, FakeKeyguardBouncerRepository(), FakeConfigurationRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt index 7f990a446aaf..f9241343549c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt @@ -26,7 +26,6 @@ import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory @@ -106,8 +105,7 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { onActionStarted.run() } - val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) } - val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags) + val withDeps = KeyguardInteractorFactory.create(featureFlags = FakeFeatureFlags()) val keyguardInteractor = withDeps.keyguardInteractor keyguardRepository = withDeps.repository diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 0d78ae9c7b11..abfff34f6dba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -23,8 +23,6 @@ import android.os.UserManager import android.provider.Settings import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.settings.FakeUserTracker import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus @@ -322,8 +320,6 @@ class UserRepositoryImplTest : SysuiTestCase() { } private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl { - val featureFlags = FakeFeatureFlags() - featureFlags.set(FACE_AUTH_REFACTOR, true) return UserRepositoryImpl( appContext = context, manager = manager, @@ -332,7 +328,6 @@ class UserRepositoryImplTest : SysuiTestCase() { backgroundDispatcher = IMMEDIATE, globalSettings = globalSettings, tracker = tracker, - featureFlags = featureFlags, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt index 017eefe38f2a..bf851eb69a0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt @@ -121,8 +121,6 @@ class UserSwitcherInteractorTest : SysuiTestCase() { ) utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false) - utils.featureFlags.set(Flags.FACE_AUTH_REFACTOR, true) - spyContext = spy(context) keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.featureFlags) keyguardRepository = keyguardReply.repository diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index 7041eab9d247..d1870b1d8fcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -233,11 +233,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { } private fun viewModel(): StatusBarUserChipViewModel { - val featureFlags = - FakeFeatureFlags().apply { - set(Flags.FULL_SCREEN_USER_SWITCHER, false) - set(Flags.FACE_AUTH_REFACTOR, true) - } + val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } runBlocking { userRepository.setUserInfos(listOf(USER_0)) userRepository.setSelectedUserInfo(USER_0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 686f492fde50..b7b24f6dc7dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -147,11 +147,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { resetOrExitSessionReceiver = resetOrExitSessionReceiver, ) - val featureFlags = - FakeFeatureFlags().apply { - set(Flags.FULL_SCREEN_USER_SWITCHER, false) - set(Flags.FACE_AUTH_REFACTOR, true) - } + val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } val reply = KeyguardInteractorFactory.create(featureFlags = featureFlags) keyguardRepository = reply.repository diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index df7609c544a4..ca167ad3cd14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -146,6 +146,7 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor; @@ -366,6 +367,9 @@ public class BubblesTest extends SysuiTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + // TODO: b/312476335 - Update to check flag and instantiate old or new implementation. + mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { doReturn(true).when(mTransitions).isRegistered(); } @@ -411,7 +415,6 @@ public class BubblesTest extends SysuiTestCase { keyguardRepository, new FakeCommandQueue(), powerInteractor, - featureFlags, sceneContainerFlags, new FakeKeyguardBouncerRepository(), configurationRepository, diff --git a/packages/SystemUI/tests/utils/src/android/app/ActivityManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/ActivityManagerKosmos.kt new file mode 100644 index 000000000000..f9c920a89e16 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/app/ActivityManagerKosmos.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 android.app + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.activityManager by Kosmos.Fixture { mock<ActivityManager>() } diff --git a/packages/SystemUI/tests/utils/src/android/app/admin/DevicePolicyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/admin/DevicePolicyManagerKosmos.kt new file mode 100644 index 000000000000..b284ac0ec7e9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/app/admin/DevicePolicyManagerKosmos.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 android.app.admin + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.devicePolicyManager by Kosmos.Fixture { mock<DevicePolicyManager>() } diff --git a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt new file mode 100644 index 000000000000..f96c5080bb95 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content + +import com.android.systemui.SysuiTestableContext +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase + +val Kosmos.testableContext: SysuiTestableContext by Kosmos.Fixture { testCase.context } +var Kosmos.applicationContext: Context by Kosmos.Fixture { testableContext } diff --git a/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt new file mode 100644 index 000000000000..56867640d03d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.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 android.content.res + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos + +var Kosmos.mainResources: Resources by Kosmos.Fixture { applicationContext.resources } diff --git a/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt new file mode 100644 index 000000000000..c936b914f44e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.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 android.os + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.userManager by Kosmos.Fixture { mock<UserManager>() } diff --git a/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt new file mode 100644 index 000000000000..34c0a7915523 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.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 android.view + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos + +val Kosmos.layoutInflater: LayoutInflater by + Kosmos.Fixture { LayoutInflater.from(applicationContext) } diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.kt new file mode 100644 index 000000000000..9059da2259b1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/internal/logging/UiEventLoggerKosmos.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.internal.logging + +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.kosmos.Kosmos + +var Kosmos.uiEventLogger: UiEventLogger by Kosmos.Fixture { uiEventLoggerFake } +val Kosmos.uiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() } diff --git a/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardSecurityModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardSecurityModelKosmos.kt new file mode 100644 index 000000000000..fadcecc07190 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardSecurityModelKosmos.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.keyguard + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.keyguardSecurityModel by Kosmos.Fixture { mock<KeyguardSecurityModel>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUpdateMonitorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUpdateMonitorKosmos.kt new file mode 100644 index 000000000000..b32cbe6da0ec --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/KeyguardUpdateMonitorKosmos.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.keyguard + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.keyguardUpdateMonitor by Kosmos.Fixture { mock<KeyguardUpdateMonitor>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResetOrExitSessionReceiverKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResetOrExitSessionReceiverKosmos.kt new file mode 100644 index 000000000000..4c4cfd541a1f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResetOrExitSessionReceiverKosmos.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 + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.guestResetOrExitSessionReceiver by + Kosmos.Fixture { mock<GuestResetOrExitSessionReceiver>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResumeSessionReceiverKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResumeSessionReceiverKosmos.kt new file mode 100644 index 000000000000..a9855ffda8b8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/GuestResumeSessionReceiverKosmos.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 + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.guestResumeSessionReceiver by Kosmos.Fixture { mock<GuestResumeSessionReceiver>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt index d0c1267c9af0..3724291571d7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt @@ -15,6 +15,7 @@ */ package com.android.systemui +import android.content.ContentResolver import android.content.Context import android.content.res.Resources import android.testing.TestableContext @@ -80,6 +81,9 @@ interface SysUITestModule { test.fakeBroadcastDispatcher @Provides + fun provideContentResolver(context: Context): ContentResolver = context.contentResolver + + @Provides fun provideBaseShadeInteractor( sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt index 37a4f6181921..f57ace9bfdc6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt @@ -55,7 +55,9 @@ import com.android.systemui.statusbar.phone.LSShadeTransitionLogger import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.util.mockito.mock import com.android.wm.shell.bubbles.Bubbles import dagger.Binds @@ -100,6 +102,10 @@ data class TestMocksModule( @get:Provides val keyguardViewController: KeyguardViewController = mock(), @get:Provides val dialogLaunchAnimator: DialogLaunchAnimator = mock(), @get:Provides val sysuiState: SysUiState = mock(), + @get:Provides + val unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> = + Optional.empty(), + @get:Provides val zenModeController: ZenModeController = mock(), // log buffers @get:[Provides BroadcastDispatcherLog] diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryKosmos.kt new file mode 100644 index 000000000000..ea93e949c83c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryKosmos.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.authentication.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import kotlinx.coroutines.test.currentTime + +var Kosmos.authenticationRepository: AuthenticationRepository by + Kosmos.Fixture { fakeAuthenticationRepository } +val Kosmos.fakeAuthenticationRepository by + Kosmos.Fixture { FakeAuthenticationRepository { testScope.currentTime } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt new file mode 100644 index 000000000000..060ca4c7e912 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.authentication.domain.interactor + +import com.android.systemui.authentication.data.repository.authenticationRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.user.data.repository.userRepository +import com.android.systemui.util.time.fakeSystemClock + +val Kosmos.authenticationInteractor by + Kosmos.Fixture { + AuthenticationInteractor( + applicationScope = applicationCoroutineScope, + repository = authenticationRepository, + backgroundDispatcher = testDispatcher, + userRepository = userRepository, + clock = fakeSystemClock, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryKosmos.kt new file mode 100644 index 000000000000..2a870749644a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryKosmos.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.bouncer.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.keyguardBouncerRepository: KeyguardBouncerRepository by + Kosmos.Fixture { fakeKeyguardBouncerRepository } +val Kosmos.fakeKeyguardBouncerRepository by Kosmos.Fixture { FakeKeyguardBouncerRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingCollectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingCollectorKosmos.kt new file mode 100644 index 000000000000..3a72d11cdc18 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingCollectorKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.classifier + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.falsingCollector by Kosmos.Fixture { FalsingCollectorFake() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt new file mode 100644 index 000000000000..86a8ae5f9cf4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.common.ui + +import android.content.applicationContext +import android.view.layoutInflater +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.policy.configurationController + +val Kosmos.configurationState: ConfigurationState by + Kosmos.Fixture { + ConfigurationState(configurationController, applicationContext, layoutInflater) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryKosmos.kt new file mode 100644 index 000000000000..77b8bd4ff2de --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryKosmos.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.common.ui.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.configurationRepository: ConfigurationRepository by + Kosmos.Fixture { fakeConfigurationRepository } +val Kosmos.fakeConfigurationRepository by Kosmos.Fixture { FakeConfigurationRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt index b27926c8ba50..faacce64b2e4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt @@ -65,6 +65,7 @@ object CommunalInteractorFactory { widgetRepository, mediaRepository, smartspaceRepository, + withDeps.keyguardInteractor, appWidgetHost, editWidgetsActivityStarter, ), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt index 36f088214153..8c653a535e29 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt @@ -26,12 +26,14 @@ import com.android.systemui.shade.data.repository.FakeShadeDataLayerModule import com.android.systemui.statusbar.data.FakeStatusBarDataLayerModule import com.android.systemui.telephony.data.FakeTelephonyDataLayerModule import com.android.systemui.user.data.FakeUserDataLayerModule +import com.android.systemui.util.animation.data.FakeAnimationUtilDataLayerModule import dagger.Module @Module( includes = [ FakeAccessibilityDataLayerModule::class, + FakeAnimationUtilDataLayerModule::class, FakeAuthenticationDataLayerModule::class, FakeBouncerDataLayerModule::class, FakeCommonDataLayerModule::class, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryKosmos.kt new file mode 100644 index 000000000000..3da0681dc45f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryKosmos.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.deviceentry.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.deviceEntryRepository: DeviceEntryRepository by + Kosmos.Fixture { fakeDeviceEntryRepository } +val Kosmos.fakeDeviceEntryRepository by Kosmos.Fixture { FakeDeviceEntryRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt new file mode 100644 index 000000000000..b600b50b8d2d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.authentication.domain.interactor.authenticationInteractor +import com.android.systemui.deviceentry.data.repository.deviceEntryRepository +import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.trustRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.sceneContainerFlags +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.deviceEntryInteractor by + Kosmos.Fixture { + DeviceEntryInteractor( + applicationScope = applicationCoroutineScope, + repository = deviceEntryRepository, + authenticationInteractor = authenticationInteractor, + sceneInteractor = sceneInteractor, + deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository, + trustRepository = trustRepository, + flags = sceneContainerFlags, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt index a78076338c79..e6b7f62c7d5f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt @@ -18,4 +18,4 @@ package com.android.systemui.flags import com.android.systemui.kosmos.Kosmos -val Kosmos.featureFlags by Kosmos.Fixture { FakeFeatureFlagsClassic() } +val Kosmos.featureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt new file mode 100644 index 000000000000..3d729675f7ab --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository by + Kosmos.Fixture { fakeDeviceEntryFaceAuthRepository } +val Kosmos.fakeDeviceEntryFaceAuthRepository by + Kosmos.Fixture { FakeDeviceEntryFaceAuthRepository() } 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 df31a12b8415..1381464c6ad1 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 @@ -47,7 +47,7 @@ class FakeBiometricSettingsRepository @Inject constructor() : BiometricSettingsR get() = _isFaceAuthCurrentlyAllowed private val _isFaceAuthSupportedInCurrentPosture = MutableStateFlow(false) - override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> + override val isFaceAuthSupportedInCurrentPosture: StateFlow<Boolean> get() = _isFaceAuthSupportedInCurrentPosture override val isCurrentUserInLockdown: Flow<Boolean> diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/InWindowLauncherUnlockAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/InWindowLauncherUnlockAnimationRepositoryKosmos.kt new file mode 100644 index 000000000000..b0e4ba06d7b2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/InWindowLauncherUnlockAnimationRepositoryKosmos.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.keyguard.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.inWindowLauncherUnlockAnimationRepository by + Kosmos.Fixture { InWindowLauncherUnlockAnimationRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryKosmos.kt new file mode 100644 index 000000000000..453fef5ddca9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryKosmos.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.keyguard.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.keyguardRepository: KeyguardRepository by Kosmos.Fixture { fakeKeyguardRepository } +val Kosmos.fakeKeyguardRepository by Kosmos.Fixture { FakeKeyguardRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryKosmos.kt new file mode 100644 index 000000000000..c900ac9771a7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.keyguardSurfaceBehindRepository: KeyguardSurfaceBehindRepository by + Kosmos.Fixture { fakeKeyguardSurfaceBehindRepository } +val Kosmos.fakeKeyguardSurfaceBehindRepository by + Kosmos.Fixture { FakeKeyguardSurfaceBehindRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt new file mode 100644 index 000000000000..008f79a377e0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.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.keyguard.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by + Kosmos.Fixture { fakeKeyguardTransitionRepository } +val Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/TrustRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/TrustRepositoryKosmos.kt new file mode 100644 index 000000000000..ca87acf88d01 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/TrustRepositoryKosmos.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.keyguard.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.trustRepository: TrustRepository by Kosmos.Fixture { fakeTrustRepository } +val Kosmos.fakeTrustRepository by Kosmos.Fixture { FakeTrustRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..b03d0b822161 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.shade.data.repository.shadeRepository +import dagger.Lazy + +val Kosmos.fromLockscreenTransitionInteractor by + Kosmos.Fixture { + FromLockscreenTransitionInteractor( + transitionRepository = keyguardTransitionRepository, + transitionInteractor = keyguardTransitionInteractor, + scope = applicationCoroutineScope, + keyguardInteractor = keyguardInteractor, + flags = featureFlagsClassic, + shadeRepository = shadeRepository, + powerInteractor = powerInteractor, + inWindowLauncherUnlockAnimationInteractor = + Lazy { inWindowLauncherUnlockAnimationInteractor }, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..ade3e1a82297 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.keyguard.keyguardSecurityModel +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.user.domain.interactor.selectedUserInteractor + +val Kosmos.fromPrimaryBouncerTransitionInteractor by + Kosmos.Fixture { + FromPrimaryBouncerTransitionInteractor( + transitionRepository = keyguardTransitionRepository, + transitionInteractor = keyguardTransitionInteractor, + scope = applicationCoroutineScope, + keyguardInteractor = keyguardInteractor, + flags = featureFlagsClassic, + keyguardSecurityModel = keyguardSecurityModel, + selectedUserInteractor = selectedUserInteractor, + powerInteractor = powerInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorKosmos.kt new file mode 100644 index 000000000000..dbbb203b0570 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorKosmos.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.inWindowLauncherUnlockAnimationRepository +import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.shared.system.activityManagerWrapper +import dagger.Lazy + +val Kosmos.inWindowLauncherUnlockAnimationInteractor by + Kosmos.Fixture { + InWindowLauncherUnlockAnimationInteractor( + repository = inWindowLauncherUnlockAnimationRepository, + scope = applicationCoroutineScope, + transitionInteractor = keyguardTransitionInteractor, + surfaceBehindRepository = Lazy { keyguardSurfaceBehindRepository }, + activityManager = activityManagerWrapper, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt index 6c2ce7139375..3d8ae1e9801a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt @@ -81,6 +81,7 @@ object KeyguardDismissInteractorFactory { trustRepository, testScope.backgroundScope, mock(SelectedUserInteractor::class.java), + mock(KeyguardFaceAuthInteractor::class.java), ) val alternateBouncerInteractor = AlternateBouncerInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt index d2ff9bc5f3eb..c575bb3fe25d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt @@ -20,7 +20,6 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.power.domain.interactor.PowerInteractor @@ -40,7 +39,7 @@ object KeyguardInteractorFactory { @JvmOverloads @JvmStatic fun create( - featureFlags: FakeFeatureFlags = createFakeFeatureFlags(), + featureFlags: FakeFeatureFlags = FakeFeatureFlags(), sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(), repository: FakeKeyguardRepository = FakeKeyguardRepository(), commandQueue: FakeCommandQueue = FakeCommandQueue(), @@ -62,7 +61,6 @@ object KeyguardInteractorFactory { KeyguardInteractor( repository = repository, commandQueue = commandQueue, - featureFlags = featureFlags, sceneContainerFlags = sceneContainerFlags, bouncerRepository = bouncerRepository, configurationRepository = configurationRepository, @@ -73,11 +71,6 @@ object KeyguardInteractorFactory { ) } - /** Provide defaults, otherwise tests will throw an error */ - private fun createFakeFeatureFlags(): FakeFeatureFlags { - return FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) } - } - data class WithDependencies( val repository: FakeKeyguardRepository, val commandQueue: FakeCommandQueue, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt new file mode 100644 index 000000000000..bb840362185f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.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.domain.interactor + +import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.sceneContainerFlags +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.statusbar.commandQueue + +val Kosmos.keyguardInteractor by + Kosmos.Fixture { + KeyguardInteractor( + repository = keyguardRepository, + commandQueue = commandQueue, + powerInteractor = powerInteractor, + sceneContainerFlags = sceneContainerFlags, + bouncerRepository = keyguardBouncerRepository, + configurationRepository = configurationRepository, + shadeRepository = shadeRepository, + sceneInteractorProvider = { sceneInteractor }, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..e4d115e16b6a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import dagger.Lazy + +val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by + Kosmos.Fixture { + KeyguardTransitionInteractor( + scope = applicationCoroutineScope, + repository = keyguardTransitionRepository, + keyguardInteractor = Lazy { keyguardInteractor }, + fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor }, + fromPrimaryBouncerTransitionInteractor = + Lazy { fromPrimaryBouncerTransitionInteractor }, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt index b05915c4f678..0b1385865d63 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt @@ -1,16 +1,11 @@ package com.android.systemui.kosmos -import android.content.Context -import android.os.UserManager +import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import org.mockito.Mockito var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() } var Kosmos.testScope by Fixture { TestScope(testDispatcher) } -var Kosmos.context by Fixture<Context>() -var Kosmos.lifecycleScope by Fixture<CoroutineScope>() - -val Kosmos.userManager by Fixture { Mockito.mock(UserManager::class.java) } +var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope } +var Kosmos.testCase: SysuiTestCase by Fixture() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt new file mode 100644 index 000000000000..0ec8d49ec29b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.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.plugins + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.activityStarter by Kosmos.Fixture { mock<ActivityStarter>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt new file mode 100644 index 000000000000..cac2646a58f2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.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.plugins.statusbar + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.statusBarStateController by Kosmos.Fixture { mock<StatusBarStateController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/PowerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/PowerRepositoryKosmos.kt new file mode 100644 index 000000000000..c924579d43e5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/PowerRepositoryKosmos.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.power.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.powerRepository: PowerRepository by Kosmos.Fixture { fakePowerRepository } +val Kosmos.fakePowerRepository by Kosmos.Fixture { FakePowerRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt new file mode 100644 index 000000000000..8486691a7b95 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.power.domain.interactor + +import com.android.systemui.classifier.falsingCollector +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.power.data.repository.powerRepository +import com.android.systemui.statusbar.phone.screenOffAnimationController + +val Kosmos.powerInteractor by + Kosmos.Fixture { + PowerInteractor( + repository = powerRepository, + falsingCollector = falsingCollector, + screenOffAnimationController = screenOffAnimationController, + statusBarStateController = statusBarStateController, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 80c38b2d228c..3c96051a718f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -120,7 +120,6 @@ class SceneTestUtils( val testScope = kosmos.testScope val featureFlags = FakeFeatureFlagsClassic().apply { - set(Flags.FACE_AUTH_REFACTOR, false) set(Flags.FULL_SCREEN_USER_SWITCHER, false) set(Flags.NSSL_DEBUG_LINES, false) } @@ -245,7 +244,6 @@ class SceneTestUtils( return KeyguardInteractor( repository = repository, commandQueue = FakeCommandQueue(), - featureFlags = featureFlags, sceneContainerFlags = sceneContainerFlags, bouncerRepository = FakeKeyguardBouncerRepository(), configurationRepository = configurationRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt new file mode 100644 index 000000000000..7c4e160f6d05 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.shared.model.sceneContainerConfig + +val Kosmos.sceneContainerRepository by + Kosmos.Fixture { SceneContainerRepository(applicationCoroutineScope, sceneContainerConfig) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt new file mode 100644 index 000000000000..998987602234 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.logger.sceneLogger + +val Kosmos.sceneInteractor by + Kosmos.Fixture { + SceneInteractor( + applicationScope = applicationCoroutineScope, + repository = sceneContainerRepository, + powerInteractor = powerInteractor, + logger = sceneLogger, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt new file mode 100644 index 000000000000..a3ceef021c59 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.shared.flag + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.sceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/logger/SceneLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/logger/SceneLoggerKosmos.kt new file mode 100644 index 000000000000..c5f24f4f049d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/logger/SceneLoggerKosmos.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.scene.shared.logger + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.sceneLogger by Kosmos.Fixture { mock<SceneLogger>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt new file mode 100644 index 000000000000..f9cdc1bea309 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.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.scene.shared.model + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.sceneContainerConfig by + Kosmos.Fixture { FakeSceneContainerConfigModule().sceneContainerConfig } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeRepositoryKosmos.kt new file mode 100644 index 000000000000..38cedbca3886 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeRepositoryKosmos.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.shade.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.shadeRepository: ShadeRepository by Kosmos.Fixture { fakeShadeRepository } +val Kosmos.fakeShadeRepository by Kosmos.Fixture { FakeShadeRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt new file mode 100644 index 000000000000..7da57f024ec7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.domain.interactor + +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.sceneContainerFlags +import com.android.systemui.shade.ShadeModule +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor +import com.android.systemui.statusbar.phone.dozeParameters +import com.android.systemui.statusbar.pipeline.mobile.data.repository.userSetupRepository +import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository +import com.android.systemui.user.domain.interactor.userSwitcherInteractor + +var Kosmos.baseShadeInteractor: BaseShadeInteractor by + Kosmos.Fixture { + ShadeModule.provideBaseShadeInteractor( + sceneContainerFlags = sceneContainerFlags, + sceneContainerOn = { shadeInteractorSceneContainerImpl }, + sceneContainerOff = { shadeInteractorLegacyImpl }, + ) + } +val Kosmos.shadeInteractorSceneContainerImpl by + Kosmos.Fixture { + ShadeInteractorSceneContainerImpl( + scope = applicationCoroutineScope, + sceneInteractor = sceneInteractor, + sharedNotificationContainerInteractor = sharedNotificationContainerInteractor, + ) + } +val Kosmos.shadeInteractorLegacyImpl by + Kosmos.Fixture { + ShadeInteractorLegacyImpl( + scope = applicationCoroutineScope, + keyguardRepository = keyguardRepository, + sharedNotificationContainerInteractor = sharedNotificationContainerInteractor, + repository = shadeRepository + ) + } +var Kosmos.shadeInteractor: ShadeInteractor by Kosmos.Fixture { shadeInteractorImpl } +val Kosmos.shadeInteractorImpl by + Kosmos.Fixture { + ShadeInteractorImpl( + scope = applicationCoroutineScope, + deviceProvisioningRepository = deviceProvisioningRepository, + disableFlagsRepository = disableFlagsRepository, + dozeParams = dozeParameters, + keyguardRepository = fakeKeyguardRepository, + keyguardTransitionInteractor = keyguardTransitionInteractor, + powerInteractor = powerInteractor, + userSetupRepository = userSetupRepository, + userSwitcherInteractor = userSwitcherInteractor, + baseShadeInteractor = baseShadeInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/ActivityManagerWrapperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/ActivityManagerWrapperKosmos.kt new file mode 100644 index 000000000000..e75359386b2f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/ActivityManagerWrapperKosmos.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.shared.system + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.activityManagerWrapper by Kosmos.Fixture { mock<ActivityManagerWrapper>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt new file mode 100644 index 000000000000..27f7f6823cc7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.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 + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.commandQueue by Kosmos.Fixture { mock<CommandQueue>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt index f25d282208f0..60690838fcdf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt @@ -16,14 +16,33 @@ package com.android.systemui.statusbar.data.repository +import android.view.Display +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.data.model.StatusBarAppearance import com.android.systemui.statusbar.data.model.StatusBarMode +import com.google.common.truth.Truth.assertThat import dagger.Binds import dagger.Module import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow -class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepository { +@SysUISingleton +class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepositoryStore { + + companion object { + const val DISPLAY_ID = Display.DEFAULT_DISPLAY + } + + override val defaultDisplay: FakeStatusBarModePerDisplayRepository = + FakeStatusBarModePerDisplayRepository() + + override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository { + assertThat(displayId).isEqualTo(DISPLAY_ID) + return defaultDisplay + } +} + +class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository { override val isTransientShown = MutableStateFlow(false) override val isInFullscreenMode = MutableStateFlow(false) override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null) @@ -39,5 +58,5 @@ class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepositor @Module interface FakeStatusBarModeRepositoryModule { - @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepository + @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepositoryStore } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepositoryKosmos.kt new file mode 100644 index 000000000000..10151ac92c35 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepositoryKosmos.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.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.notificationListenerSettingsRepository by + Kosmos.Fixture { NotificationListenerSettingsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryKosmos.kt new file mode 100644 index 000000000000..a373a8e1844d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryKosmos.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.disableflags.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.disableFlagsRepository: DisableFlagsRepository by + Kosmos.Fixture { fakeDisableFlagsRepository } +val Kosmos.fakeDisableFlagsRepository by Kosmos.Fixture { FakeDisableFlagsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt new file mode 100644 index 000000000000..9851b0ef9e94 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.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.statusbar.notification.data.model + +import android.graphics.drawable.Icon +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel + +/** Simple ActiveNotificationModel builder for use in tests. */ +fun activeNotificationModel( + key: String, + groupKey: String? = null, + isAmbient: Boolean = false, + isRowDismissed: Boolean = false, + isSilent: Boolean = false, + isLastMessageFromReply: Boolean = false, + isSuppressedFromStatusBar: Boolean = false, + isPulsing: Boolean = false, + aodIcon: Icon? = null, + shelfIcon: Icon? = null, + statusBarIcon: Icon? = null, +) = + ActiveNotificationModel( + key = key, + groupKey = groupKey, + isAmbient = isAmbient, + isRowDismissed = isRowDismissed, + isSilent = isSilent, + isLastMessageFromReply = isLastMessageFromReply, + isSuppressedFromStatusBar = isSuppressedFromStatusBar, + isPulsing = isPulsing, + aodIcon = aodIcon, + shelfIcon = shelfIcon, + statusBarIcon = statusBarIcon, + ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt new file mode 100644 index 000000000000..cb1ba206d110 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.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.statusbar.notification.data.repository + +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel + +/** + * Make the repository hold [count] active notifications for testing. The keys of the notifications + * are "0", "1", ..., (count - 1).toString(). + */ +fun ActiveNotificationListRepository.setActiveNotifs(count: Int) { + this.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { repeat(count) { i -> addEntry(activeNotificationModel(key = i.toString())) } } + .build() +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryKosmos.kt new file mode 100644 index 000000000000..5507d6c2c90d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.activeNotificationListRepository by Kosmos.Fixture { ActiveNotificationListRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepositoryKosmos.kt new file mode 100644 index 000000000000..ed62fda6fc99 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepositoryKosmos.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.notification.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.headsUpNotificationIconViewStateRepository by + Kosmos.Fixture { HeadsUpNotificationIconViewStateRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt new file mode 100644 index 000000000000..f2b9da413c22 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.notificationsKeyguardViewStateRepository: NotificationsKeyguardViewStateRepository by + Kosmos.Fixture { fakeNotificationsKeyguardViewStateRepository } +val Kosmos.fakeNotificationsKeyguardViewStateRepository by + Kosmos.Fixture { FakeNotificationsKeyguardViewStateRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt new file mode 100644 index 000000000000..3d7fb6d91393 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.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.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository + +val Kosmos.activeNotificationsInteractor by + Kosmos.Fixture { ActiveNotificationsInteractor(activeNotificationListRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractorKosmos.kt new file mode 100644 index 000000000000..d14c8548f3cf --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractorKosmos.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.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.data.repository.headsUpNotificationIconViewStateRepository + +val Kosmos.headsUpNotificationIconInteractor by + Kosmos.Fixture { HeadsUpNotificationIconInteractor(headsUpNotificationIconViewStateRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt new file mode 100644 index 000000000000..e7bd5ea2b174 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.icon.domain.interactor + +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository +import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.wm.shell.bubbles.bubblesOptional +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.alwaysOnDisplayNotificationIconsInteractor by + Kosmos.Fixture { + AlwaysOnDisplayNotificationIconsInteractor( + deviceEntryInteractor = deviceEntryInteractor, + iconsInteractor = notificationIconsInteractor, + ) + } +val Kosmos.statusBarNotificationIconsInteractor by + Kosmos.Fixture { + StatusBarNotificationIconsInteractor( + iconsInteractor = notificationIconsInteractor, + settingsRepository = notificationListenerSettingsRepository, + ) + } +val Kosmos.notificationIconsInteractor by + Kosmos.Fixture { + NotificationIconsInteractor( + activeNotificationsInteractor = activeNotificationsInteractor, + bubbles = bubblesOptional, + keyguardViewStateRepository = notificationsKeyguardViewStateRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelKosmos.kt new file mode 100644 index 000000000000..6295b83a2bd0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelKosmos.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.viewmodel + +import android.content.res.mainResources +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notification.icon.domain.interactor.alwaysOnDisplayNotificationIconsInteractor + +val Kosmos.notificationIconContainerAlwaysOnDisplayViewModel by + Kosmos.Fixture { + NotificationIconContainerAlwaysOnDisplayViewModel( + iconsInteractor = alwaysOnDisplayNotificationIconsInteractor, + keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + resources = mainResources, + shadeInteractor = shadeInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelKosmos.kt new file mode 100644 index 000000000000..04bb52d101db --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelKosmos.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.viewmodel + +import android.content.res.mainResources +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor +import com.android.systemui.statusbar.notification.icon.domain.interactor.statusBarNotificationIconsInteractor +import com.android.systemui.statusbar.phone.domain.interactor.darkIconInteractor + +val Kosmos.notificationIconContainerStatusBarViewModel by + Kosmos.Fixture { + NotificationIconContainerStatusBarViewModel( + darkIconInteractor = darkIconInteractor, + iconsInteractor = statusBarNotificationIconsInteractor, + headsUpIconInteractor = headsUpNotificationIconInteractor, + keyguardInteractor = keyguardInteractor, + notificationsInteractor = activeNotificationsInteractor, + resources = mainResources, + shadeInteractor = shadeInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt new file mode 100644 index 000000000000..3403227f6d27 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.stack.domain.interactor + +import android.content.applicationContext +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.policy.splitShadeStateController + +val Kosmos.sharedNotificationContainerInteractor by + Kosmos.Fixture { + SharedNotificationContainerInteractor( + configurationRepository = configurationRepository, + context = applicationContext, + splitShadeStateController = splitShadeStateController, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeParametersKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeParametersKosmos.kt new file mode 100644 index 000000000000..9f6b181f8348 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeParametersKosmos.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.phone + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.dozeParameters by Kosmos.Fixture { mock<DozeParameters>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ScreenOffAnimationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ScreenOffAnimationControllerKosmos.kt new file mode 100644 index 000000000000..d4c21f66d394 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ScreenOffAnimationControllerKosmos.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.phone + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.screenOffAnimationController by Kosmos.Fixture { mock<ScreenOffAnimationController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepositoryKosmos.kt new file mode 100644 index 000000000000..977dcb738ba1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepositoryKosmos.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.phone.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.darkIconRepository: DarkIconRepository by Kosmos.Fixture { fakeDarkIconRepository } +val Kosmos.fakeDarkIconRepository by Kosmos.Fixture { FakeDarkIconRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractorKosmos.kt new file mode 100644 index 000000000000..db678d487b3d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractorKosmos.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.phone.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.phone.data.repository.darkIconRepository + +val Kosmos.darkIconInteractor by Kosmos.Fixture { DarkIconInteractor(darkIconRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt new file mode 100644 index 000000000000..7b9634a7abb5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.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.pipeline.mobile.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository } +val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt new file mode 100644 index 000000000000..18a2f9482df8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.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.policy + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerKosmos.kt new file mode 100644 index 000000000000..6a77c882eaa1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerKosmos.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.policy + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.deviceProvisionedController: DeviceProvisionedController by + Kosmos.Fixture { fakeDeviceProvisionedController } +val Kosmos.fakeDeviceProvisionedController by Kosmos.Fixture { FakeDeviceProvisionedController() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerKosmos.kt new file mode 100644 index 000000000000..5e430381e293 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.splitShadeStateController: SplitShadeStateController by + Kosmos.Fixture { resourcesSplitShadeStateController } +val Kosmos.resourcesSplitShadeStateController by + Kosmos.Fixture { ResourcesSplitShadeStateController() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryKosmos.kt new file mode 100644 index 000000000000..56a0e02e41dc --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryKosmos.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.policy.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.deviceProvisioningRepository: DeviceProvisioningRepository by + Kosmos.Fixture { fakeDeviceProvisioningRepository } +val Kosmos.fakeDeviceProvisioningRepository by Kosmos.Fixture { FakeDeviceProvisioningRepository() } 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 index 405993073b68..c4d78678e59a 100644 --- 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 @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.policy.data.repository -import android.app.NotificationManager +import android.app.NotificationManager.Policy import android.provider.Settings import com.android.systemui.dagger.SysUISingleton import dagger.Binds @@ -27,14 +27,24 @@ 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?> = + override val consolidatedNotificationPolicy: MutableStateFlow<Policy?> = MutableStateFlow( - NotificationManager.Policy( + Policy( /* priorityCategories = */ 0, /* priorityCallSenders = */ 0, /* priorityMessageSenders = */ 0, ) ) + + fun setSuppressedVisualEffects(suppressedVisualEffects: Int) { + consolidatedNotificationPolicy.value = + Policy( + /* priorityCategories = */ 0, + /* priorityCallSenders = */ 0, + /* priorityMessageSenders = */ 0, + /* suppressedVisualEffects = */ suppressedVisualEffects, + ) + } } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryKosmos.kt new file mode 100644 index 000000000000..6bb5ec5f0313 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryKosmos.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.telephony.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.telephonyRepository: TelephonyRepository by Kosmos.Fixture { fakeTelephonyRepository } +val Kosmos.fakeTelephonyRepository by Kosmos.Fixture { FakeTelephonyRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractorKosmos.kt new file mode 100644 index 000000000000..02ca96e3521b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractorKosmos.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.telephony.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.telephony.data.repository.telephonyRepository + +val Kosmos.telephonyInteractor by Kosmos.Fixture { TelephonyInteractor(telephonyRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt index 8bce9b6d461d..9bb52623bdcd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserRepositoryKosmos.kt @@ -18,4 +18,5 @@ package com.android.systemui.user.data.repository import com.android.systemui.kosmos.Kosmos -val Kosmos.userRepository by Kosmos.Fixture { FakeUserRepository() } +var Kosmos.userRepository: UserRepository by Kosmos.Fixture { fakeUserRepository } +val Kosmos.fakeUserRepository by Kosmos.Fixture { FakeUserRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt index e69570433d43..3b1c3f0198c7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/GuestUserInteractorKosmos.kt @@ -16,28 +16,32 @@ package com.android.systemui.user.domain.interactor +import android.app.admin.devicePolicyManager +import android.content.applicationContext +import android.os.userManager +import com.android.internal.logging.uiEventLogger +import com.android.systemui.guestResetOrExitSessionReceiver +import com.android.systemui.guestResumeSessionReceiver import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.context -import com.android.systemui.kosmos.lifecycleScope +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.kosmos.userManager +import com.android.systemui.statusbar.policy.deviceProvisionedController import com.android.systemui.user.data.repository.userRepository -import com.android.systemui.util.mockito.mock val Kosmos.guestUserInteractor by Kosmos.Fixture { GuestUserInteractor( - applicationContext = context, - applicationScope = lifecycleScope, + applicationContext = applicationContext, + applicationScope = applicationCoroutineScope, mainDispatcher = testDispatcher, backgroundDispatcher = testDispatcher, manager = userManager, - deviceProvisionedController = mock(), repository = userRepository, - devicePolicyManager = mock(), + deviceProvisionedController = deviceProvisionedController, + devicePolicyManager = devicePolicyManager, refreshUsersScheduler = refreshUsersScheduler, - uiEventLogger = mock(), - resumeSessionReceiver = mock(), - resetOrExitSessionReceiver = mock(), + uiEventLogger = uiEventLogger, + resumeSessionReceiver = guestResumeSessionReceiver, + resetOrExitSessionReceiver = guestResetOrExitSessionReceiver, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeKosmos.kt new file mode 100644 index 000000000000..de9f69bc9215 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.user.domain.interactor + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.headlessSystemUserMode by Kosmos.Fixture { HeadlessSystemUserModeImpl() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt index 87a2fe0249e3..14da8b0f461c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerKosmos.kt @@ -17,15 +17,11 @@ package com.android.systemui.user.domain.interactor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.lifecycleScope +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.user.data.repository.userRepository val Kosmos.refreshUsersScheduler by Kosmos.Fixture { - RefreshUsersScheduler( - applicationScope = lifecycleScope, - mainDispatcher = testDispatcher, - repository = userRepository, - ) + RefreshUsersScheduler(applicationCoroutineScope, testDispatcher, userRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt new file mode 100644 index 000000000000..427f92a7f514 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.user.domain.interactor + +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.user.data.repository.userRepository + +val Kosmos.selectedUserInteractor by + Kosmos.Fixture { SelectedUserInteractor(userRepository, featureFlagsClassic) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt index 6d6b2683a7ea..42c77aaac53f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt @@ -16,42 +16,41 @@ package com.android.systemui.user.domain.interactor +import android.app.activityManager +import android.content.applicationContext +import android.os.userManager +import com.android.internal.logging.uiEventLogger +import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.broadcast.broadcastDispatcher -import com.android.systemui.flags.featureFlags -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.context -import com.android.systemui.kosmos.lifecycleScope +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.kosmos.userManager -import com.android.systemui.telephony.data.repository.FakeTelephonyRepository -import com.android.systemui.telephony.domain.interactor.TelephonyInteractor +import com.android.systemui.plugins.activityStarter +import com.android.systemui.telephony.domain.interactor.telephonyInteractor import com.android.systemui.user.data.repository.userRepository -import com.android.systemui.util.mockito.mock +import com.android.systemui.utils.userRestrictionChecker val Kosmos.userSwitcherInteractor by Kosmos.Fixture { UserSwitcherInteractor( - applicationContext = context, + applicationContext = applicationContext, repository = userRepository, - activityStarter = mock(), - keyguardInteractor = - KeyguardInteractorFactory.create(featureFlags = featureFlags).keyguardInteractor, - featureFlags = featureFlags, + activityStarter = activityStarter, + keyguardInteractor = keyguardInteractor, + featureFlags = featureFlagsClassic, manager = userManager, - headlessSystemUserMode = mock(), - applicationScope = lifecycleScope, - telephonyInteractor = - TelephonyInteractor( - repository = FakeTelephonyRepository(), - ), + headlessSystemUserMode = headlessSystemUserMode, + applicationScope = applicationCoroutineScope, + telephonyInteractor = telephonyInteractor, broadcastDispatcher = broadcastDispatcher, - keyguardUpdateMonitor = mock(), + keyguardUpdateMonitor = keyguardUpdateMonitor, backgroundDispatcher = testDispatcher, - activityManager = mock(), + activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestUserInteractor, - uiEventLogger = mock(), - userRestrictionChecker = mock() + uiEventLogger = uiEventLogger, + userRestrictionChecker = userRestrictionChecker, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt new file mode 100644 index 000000000000..f7830d9655a7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.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.util.animation.data + +import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepositoryModule +import dagger.Module + +@Module(includes = [FakeAnimationStatusRepositoryModule::class]) +object FakeAnimationUtilDataLayerModule diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/FakeAnimationStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/repository/FakeAnimationStatusRepository.kt index e72235ca508f..ca6628b7f357 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/FakeAnimationStatusRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/repository/FakeAnimationStatusRepository.kt @@ -11,15 +11,17 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.systemui.util.animation +package com.android.systemui.util.animation.data.repository -import com.android.systemui.util.animation.data.repository.AnimationStatusRepository +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow -class FakeAnimationStatusRepository : AnimationStatusRepository { +class FakeAnimationStatusRepository @Inject constructor() : AnimationStatusRepository { // Replay 1 element as real repository always emits current status as a first element private val animationsEnabled: MutableSharedFlow<Boolean> = MutableSharedFlow(replay = 1) @@ -30,3 +32,8 @@ class FakeAnimationStatusRepository : AnimationStatusRepository { animationsEnabled.tryEmit(enabled) } } + +@Module +interface FakeAnimationStatusRepositoryModule { + @Binds fun bindFake(fake: FakeAnimationStatusRepository): AnimationStatusRepository +} diff --git a/core/java/android/os/WorkDuration.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt index 0f61204d72c4..914e65427f41 100644 --- a/core/java/android/os/WorkDuration.aidl +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt @@ -14,6 +14,8 @@ * limitations under the License. */ -package android.os; +package com.android.systemui.util.time -parcelable WorkDuration cpp_header "android/WorkDuration.h";
\ No newline at end of file +import com.android.systemui.kosmos.Kosmos + +var Kosmos.fakeSystemClock by Kosmos.Fixture { FakeSystemClock() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/UserRestrictionCheckerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/UserRestrictionCheckerKosmos.kt new file mode 100644 index 000000000000..24d5d2f72289 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/UserRestrictionCheckerKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.utils + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.userRestrictionChecker by Kosmos.Fixture { UserRestrictionChecker() } diff --git a/packages/SystemUI/tests/utils/src/com/android/wm/shell/bubbles/BubblesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/wm/shell/bubbles/BubblesKosmos.kt new file mode 100644 index 000000000000..a7a37b22c969 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/wm/shell/bubbles/BubblesKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock +import java.util.Optional + +var Kosmos.bubblesOptional by Kosmos.Fixture { Optional.of(bubbles) } +var Kosmos.bubbles by Kosmos.Fixture { mock<Bubbles> {} } diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java index 1d315798d647..f02f06c056bd 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java @@ -18,7 +18,6 @@ package android.ravenwood.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -32,7 +31,7 @@ import java.lang.annotation.Target; * * @hide */ -@Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) +@Target({FIELD, METHOD, CONSTRUCTOR}) @Retention(RetentionPolicy.CLASS) public @interface RavenwoodKeep { } diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java new file mode 100644 index 000000000000..784727410188 --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.ravenwood.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + * + * @hide + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodKeepPartialClass { +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java new file mode 100644 index 000000000000..eeebee985e4a --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.ravenwood.annotation; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * @hide + */ +@Target(TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodKeepStaticInitializer { +} diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt index 28639d4960a4..48e93280f73f 100644 --- a/ravenwood/framework-minus-apex-ravenwood-policies.txt +++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt @@ -17,12 +17,14 @@ class android.util.MapCollections stubclass class android.util.Log stubclass class android.util.Log !com.android.hoststubgen.nativesubstitution.Log_host class android.util.LogPrinter stubclass +class android.util.LocalLog stubclass # String Manipulation class android.util.Printer stubclass class android.util.PrintStreamPrinter stubclass class android.util.PrintWriterPrinter stubclass class android.util.StringBuilderPrinter stubclass +class android.util.IndentingPrintWriter stubclass # Properties class android.util.Property stubclass @@ -76,6 +78,7 @@ class android.util.Patterns stubclass class android.util.UtilConfig stubclass # Internals +class com.android.internal.util.FastPrintWriter stubclass class com.android.internal.util.GrowingArrayUtils stubclass class com.android.internal.util.LineBreakBufferedWriter stubclass class com.android.internal.util.Preconditions stubclass diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt index df44fde2ed72..a791f682fecf 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -2,6 +2,12 @@ com.android.internal.util.ArrayUtils +android.util.DataUnit +android.util.EventLog +android.util.IntArray +android.util.LongArray +android.util.Slog +android.util.TimeUtils android.util.Xml android.os.Binder diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt index 4b07ef6e35a8..f842f33bc95b 100644 --- a/ravenwood/ravenwood-standard-options.txt +++ b/ravenwood/ravenwood-standard-options.txt @@ -18,6 +18,9 @@ --keep-annotation android.ravenwood.annotation.RavenwoodKeep +--keep-annotation + android.ravenwood.annotation.RavenwoodKeepPartialClass + --keep-class-annotation android.ravenwood.annotation.RavenwoodKeepWholeClass @@ -35,3 +38,6 @@ --class-load-hook-annotation android.ravenwood.annotation.RavenwoodClassLoadHook + +--keep-static-initializer-annotation + android.ravenwood.annotation.RavenwoodKeepStaticInitializer diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 5635dd59e3f1..42ab05fdd231 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -21,6 +21,7 @@ import static com.android.server.autofill.Helper.sVerbose; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityOptions; import android.app.Dialog; import android.app.PendingIntent; import android.content.ComponentName; @@ -205,7 +206,10 @@ final class SaveUi { intent, PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT, - /* options= */ null, UserHandle.CURRENT); + ActivityOptions.makeBasic() + .setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + .toBundle(), UserHandle.CURRENT); if (sDebug) { Slog.d(TAG, "startActivity add save UI restored with intent=" + intent); } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index b9c269c91651..71a1f012e5cb 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -75,7 +75,6 @@ import android.companion.IOnMessageReceivedListener; import android.companion.IOnTransportsChangedListener; import android.companion.ISystemDataTransferCallback; import android.companion.datatransfer.PermissionSyncRequest; -import android.companion.utils.FeatureUtils; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; @@ -829,11 +828,6 @@ public class CompanionDeviceManagerService extends SystemService { @Override public PendingIntent buildPermissionTransferUserConsentIntent(String packageName, int userId, int associationId) { - if (!FeatureUtils.isPermSyncEnabled()) { - throw new UnsupportedOperationException("Calling" - + " buildPermissionTransferUserConsentIntent, but this API is disabled by" - + " the system."); - } return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent( packageName, userId, associationId); } @@ -841,10 +835,6 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void startSystemDataTransfer(String packageName, int userId, int associationId, ISystemDataTransferCallback callback) { - if (!FeatureUtils.isPermSyncEnabled()) { - throw new UnsupportedOperationException("Calling startSystemDataTransfer, but this" - + " API is disabled by the system."); - } mSystemDataTransferProcessor.startSystemDataTransfer(packageName, userId, associationId, callback); } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 1f6261383961..23e7ce68c1d0 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -20,6 +20,7 @@ import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_S import android.companion.AssociationInfo; import android.companion.ContextSyncMessage; +import android.companion.Flags; import android.companion.Telecom; import android.companion.datatransfer.PermissionSyncRequest; import android.net.MacAddress; @@ -65,7 +66,14 @@ class CompanionDeviceShellCommand extends ShellCommand { public int onCommand(String cmd) { final PrintWriter out = getOutPrintWriter(); final int associationId; + try { + if ("simulate-device-event".equals(cmd) && Flags.devicePresence()) { + associationId = getNextIntArgRequired(); + int event = getNextIntArgRequired(); + mDevicePresenceMonitor.simulateDeviceEvent(associationId, event); + return 0; + } switch (cmd) { case "list": { final int userId = getNextIntArgRequired(); @@ -107,10 +115,15 @@ class CompanionDeviceShellCommand extends ShellCommand { mService.loadAssociationsFromDisk(); break; - case "simulate-device-event": + case "simulate-device-appeared": + associationId = getNextIntArgRequired(); + mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 0); + break; + + case "simulate-device-disappeared": associationId = getNextIntArgRequired(); - int event = getNextIntArgRequired(); - mDevicePresenceMonitor.simulateDeviceEvent(associationId, event); + mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1); + break; case "remove-inactive-associations": { // This command should trigger the same "clean-up" job as performed by the @@ -346,9 +359,7 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY."); pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); - pw.println(" simulate-device-event ASSOCIATION_ID EVENT"); - pw.println(" Simulate the companion device event changes:"); - pw.println(" Case(0): "); + pw.println(" simulate-device-appeared ASSOCIATION_ID"); pw.println(" Make CDM act as if the given companion device has appeared."); pw.println(" I.e. bind the associated companion application's"); pw.println(" CompanionDeviceService(s) and trigger onDeviceAppeared() callback."); @@ -356,18 +367,43 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" will act as if device disappeared, unless 'simulate-device-disappeared'"); pw.println(" or 'simulate-device-appeared' is called again before 60 seconds run out" + "."); - pw.println(" Case(1): "); + pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); + + pw.println(" simulate-device-disappeared ASSOCIATION_ID"); pw.println(" Make CDM act as if the given companion device has disappeared."); pw.println(" I.e. unbind the associated companion application's"); pw.println(" CompanionDeviceService(s) and trigger onDeviceDisappeared() callback."); pw.println(" NOTE: This will only have effect if 'simulate-device-appeared' was"); pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than"); pw.println(" 60 seconds ago."); - pw.println(" Case(2): "); - pw.println(" Make CDM act as if the given companion device is BT connected "); - pw.println(" Case(3): "); - pw.println(" Make CDM act as if the given companion device is BT disconnected "); - pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); + + if (Flags.devicePresence()) { + pw.println(" simulate-device-event ASSOCIATION_ID EVENT"); + pw.println(" Simulate the companion device event changes:"); + pw.println(" Case(0): "); + pw.println(" Make CDM act as if the given companion device has appeared."); + pw.println(" I.e. bind the associated companion application's"); + pw.println(" CompanionDeviceService(s) and trigger onDeviceAppeared() callback."); + pw.println(" The CDM will consider the devices as present for" + + "60 seconds and then"); + pw.println(" will act as if device disappeared, unless" + + "'simulate-device-disappeared'"); + pw.println(" or 'simulate-device-appeared' is called again before 60 seconds" + + "run out."); + pw.println(" Case(1): "); + pw.println(" Make CDM act as if the given companion device has disappeared."); + pw.println(" I.e. unbind the associated companion application's"); + pw.println(" CompanionDeviceService(s) and trigger onDeviceDisappeared()" + + "callback."); + pw.println(" NOTE: This will only have effect if 'simulate-device-appeared' was"); + pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than"); + pw.println(" 60 seconds ago."); + pw.println(" Case(2): "); + pw.println(" Make CDM act as if the given companion device is BT connected "); + pw.println(" Case(3): "); + pw.println(" Make CDM act as if the given companion device is BT disconnected "); + pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); + } pw.println(" remove-inactive-associations"); pw.println(" Remove self-managed associations that have not been active "); diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java index 8fea078c3183..e42b9356cca3 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -79,7 +79,7 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange void onDeviceDisappeared(int associationId); /**Invoked when device has corresponding event changes. */ - void onDeviceEvent(int associationId, int state); + void onDeviceEvent(int associationId, int event); } private final @NonNull AssociationStore mAssociationStore; 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 215970eedff1..e51ef297519f 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -864,6 +864,23 @@ public class VirtualDeviceManagerService extends SystemService { } @Override + public @NonNull Set<String> getAllPersistentDeviceIds() { + Set<String> persistentIds = new ArraySet<>(); + synchronized (mVirtualDeviceManagerLock) { + for (int i = 0; i < mActiveAssociations.size(); ++i) { + AssociationInfo associationInfo = mActiveAssociations.get(i); + if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains( + associationInfo.getDeviceProfile())) { + persistentIds.add( + VirtualDeviceImpl.createPersistentDeviceId( + associationInfo.getId())); + } + } + } + return persistentIds; + } + + @Override public void registerAppsOnVirtualDeviceListener( @NonNull AppsOnVirtualDeviceListener listener) { synchronized (mVirtualDeviceManagerLock) { 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 index a570d0989134..6940ffe40a51 100644 --- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java +++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java @@ -69,6 +69,11 @@ public final class VirtualCameraConversionUtil { } @Override + public void onProcessCaptureRequest(int streamId, int frameId) throws RemoteException { + camera.onProcessCaptureRequest(streamId, frameId, /*metadata=*/ null); + } + + @Override public void onStreamClosed(int streamId) throws RemoteException { camera.onStreamClosed(streamId); } diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 7907d616d1b5..77b6d583808c 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -1182,8 +1182,8 @@ public class BinaryTransparencyService extends SystemService { // we are only interested in doing things at PHASE_BOOT_COMPLETED if (phase == PHASE_BOOT_COMPLETED) { - Slog.i(TAG, "Boot completed. Getting VBMeta Digest."); - getVBMetaDigestInformation(); + Slog.i(TAG, "Boot completed. Getting boot integrity data."); + collectBootIntegrityInfo(); // Log to statsd // TODO(b/264061957): For now, biometric system properties are always collected if users @@ -1458,10 +1458,19 @@ public class BinaryTransparencyService extends SystemService { } } - private void getVBMetaDigestInformation() { + private void collectBootIntegrityInfo() { mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE); Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest)); FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest); + + if (android.security.Flags.binaryTransparencySepolicyHash()) { + byte[] sepolicyHash = PackageUtils.computeSha256DigestForLargeFileAsBytes( + "/sys/fs/selinux/policy", PackageUtils.createLargeFileBuffer()); + String sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false); + Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded); + FrameworkStatsLog.write(FrameworkStatsLog.BOOT_INTEGRITY_INFO_REPORTED, + sepolicyHashEncoded, mVbmetaDigest); + } } /** diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index 8cd5ce1f4ff8..ce1a8756a849 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -158,6 +158,10 @@ public final class PinnerService extends SystemService { @GuardedBy("this") private ArraySet<Integer> mPinKeys; + // Note that we don't use the `_BOOT` namespace for anonymous pinnings, as we want + // them to be responsive to dynamic flag changes for experimentation. + private static final String DEVICE_CONFIG_NAMESPACE_ANON_SIZE = + DeviceConfig.NAMESPACE_RUNTIME_NATIVE; private static final String DEVICE_CONFIG_KEY_ANON_SIZE = "pin_shared_anon_size"; private static final long DEFAULT_ANON_SIZE = SystemProperties.getLong("pinner.pin_shared_anon_size", 0); @@ -188,11 +192,11 @@ public final class PinnerService extends SystemService { } }; - private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener = + private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigAnonSizeListener = new DeviceConfig.OnPropertiesChangedListener() { @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT.equals(properties.getNamespace()) + if (DEVICE_CONFIG_NAMESPACE_ANON_SIZE.equals(properties.getNamespace()) && properties.getKeyset().contains(DEVICE_CONFIG_KEY_ANON_SIZE)) { refreshPinAnonConfig(); } @@ -246,9 +250,9 @@ public final class PinnerService extends SystemService { registerUserSetupCompleteListener(); mDeviceConfigInterface.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, + DEVICE_CONFIG_NAMESPACE_ANON_SIZE, new HandlerExecutor(mPinnerHandler), - mDeviceConfigListener); + mDeviceConfigAnonSizeListener); } @Override @@ -733,7 +737,7 @@ public final class PinnerService extends SystemService { private void refreshPinAnonConfig() { long newPinAnonSize = mDeviceConfigInterface.getLong( - DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, + DEVICE_CONFIG_NAMESPACE_ANON_SIZE, DEVICE_CONFIG_KEY_ANON_SIZE, DEFAULT_ANON_SIZE); newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE)); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 9d8f979e883c..f3b2ef34ad6d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -552,7 +552,8 @@ final class ActivityManagerShellCommand extends ShellCommand { mAsync = true; } else if (opt.equals("--splashscreen-show-icon")) { mShowSplashScreen = true; - } else if (opt.equals("--dismiss-keyguard-if-insecure")) { + } else if (opt.equals("--dismiss-keyguard-if-insecure") + || opt.equals("--dismiss-keyguard")) { mDismissKeyguardIfInsecure = true; } else { return false; diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 5b54561c2164..e07631cfbdb0 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -193,6 +193,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private @Nullable BroadcastProcessQueue mRunningColdStart; /** + * Indicates whether we have queued a message to check pending cold start validity. + */ + @GuardedBy("mService") + private boolean mCheckPendingColdStartQueued; + + /** * Collection of latches waiting for device to reach specific state. The * first argument is a function to test for the desired state, and the * second argument is the latch to release once that state is reached. @@ -302,7 +308,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { return true; } case MSG_CHECK_PENDING_COLD_START_VALIDITY: { - checkPendingColdStartValidity(); + synchronized (mService) { + /* Clear this as we have just received the broadcast. */ + mCheckPendingColdStartQueued = false; + checkPendingColdStartValidityLocked(); + } return true; } case MSG_PROCESS_FREEZABLE_CHANGED: { @@ -549,7 +559,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER); } - checkPendingColdStartValidity(); + checkPendingColdStartValidityLocked(); checkAndRemoveWaitingFor(); traceEnd(cookie); @@ -573,22 +583,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue { enqueueUpdateRunningList(); } - private void checkPendingColdStartValidity() { + @GuardedBy("mService") + private void checkPendingColdStartValidityLocked() { // There are a few cases where a starting process gets killed but AMS doesn't report // this event. So, once we start waiting for a pending cold start, periodically check // if the pending start is still valid and if not, clear it so that the queue doesn't // keep waiting for the process start forever. - synchronized (mService) { - // If there is no pending cold start, then nothing to do. - if (mRunningColdStart == null) { - return; - } - if (isPendingColdStartValid()) { + // If there is no pending cold start, then nothing to do. + if (mRunningColdStart == null) { + return; + } + if (isPendingColdStartValid()) { + if (!mCheckPendingColdStartQueued) { mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_PENDING_COLD_START_VALIDITY, mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS); - } else { - clearInvalidPendingColdStart(); + mCheckPendingColdStartQueued = true; } + } else { + clearInvalidPendingColdStart(); } } diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index b852ef56fceb..d372108e0a47 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -67,10 +67,8 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal.OomAdjReason; import android.content.pm.ServiceInfo; -import android.os.IBinder; import android.os.SystemClock; import android.os.Trace; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -80,7 +78,6 @@ import com.android.server.ServiceThread; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.function.Consumer; @@ -504,6 +501,28 @@ public class OomAdjusterModernImpl extends OomAdjuster { } } + /** + * A helper consumer for collecting processes that have not been reached yet. To avoid object + * allocations every OomAdjuster update, the results will be stored in + * {@link UnreachedProcessCollector#processList}. The process list reader is responsible + * for setting it before usage, as well as, clearing the reachable state of each process in the + * list. + */ + private static class UnreachedProcessCollector implements Consumer<ProcessRecord> { + public ArrayList<ProcessRecord> processList = null; + @Override + public void accept(ProcessRecord process) { + if (process.mState.isReachable()) { + return; + } + process.mState.setReachable(true); + processList.add(process); + } + } + + private final UnreachedProcessCollector mUnreachedProcessCollector = + new UnreachedProcessCollector(); + OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) { this(service, processList, activeUids, createAdjusterThread()); @@ -755,23 +774,8 @@ public class OomAdjusterModernImpl extends OomAdjuster { // We'll need to collect the upstream processes of the target apps here, because those // processes would potentially impact the procstate/adj via bindings. if (!fullUpdate) { - final boolean containsCycle = collectReversedReachableProcessesLocked(targetProcesses, - clientProcesses); - - // If any of its upstream processes are in a cycle, - // move them into the candidate targets. - if (containsCycle) { - // Add all client apps to the target process list. - for (int i = 0, size = clientProcesses.size(); i < size; i++) { - final ProcessRecord client = clientProcesses.get(i); - final UidRecord uidRec = client.getUidRecord(); - targetProcesses.add(client); - if (uidRec != null) { - uids.put(uidRec.getUid(), uidRec); - } - } - clientProcesses.clear(); - } + collectExcludedClientProcessesLocked(targetProcesses, clientProcesses); + for (int i = 0, size = targetProcesses.size(); i < size; i++) { final ProcessRecord app = targetProcesses.valueAt(i); app.mState.resetCachedInfo(); @@ -807,102 +811,36 @@ public class OomAdjusterModernImpl extends OomAdjuster { } /** - * Collect the reversed reachable processes from the given {@code apps}, the result will be - * returned in the given {@code processes}, which will <em>NOT</em> include the processes from - * the given {@code apps}. + * Collect the client processes from the given {@code apps}, the result will be returned in the + * given {@code clientProcesses}, which will <em>NOT</em> include the processes from the given + * {@code apps}. */ @GuardedBy("mService") - private boolean collectReversedReachableProcessesLocked(ArraySet<ProcessRecord> apps, + private void collectExcludedClientProcessesLocked(ArraySet<ProcessRecord> apps, ArrayList<ProcessRecord> clientProcesses) { - final ArrayDeque<ProcessRecord> queue = mTmpQueue; - queue.clear(); - clientProcesses.clear(); - for (int i = 0, size = apps.size(); i < size; i++) { + // Mark all of the provided apps as reachable to avoid including them in the client list. + final int appsSize = apps.size(); + for (int i = 0; i < appsSize; i++) { final ProcessRecord app = apps.valueAt(i); app.mState.setReachable(true); - app.mState.setReversedReachable(true); - queue.offer(app); } - // Track if any of them reachables could include a cycle - boolean containsCycle = false; - - // Scan upstreams of the process record - for (ProcessRecord pr = queue.poll(); pr != null; pr = queue.poll()) { - if (!pr.mState.isReachable()) { - // If not in the given initial set of apps, add it. - clientProcesses.add(pr); - } - final ProcessServiceRecord psr = pr.mServices; - for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) { - final ServiceRecord s = psr.getRunningServiceAt(i); - final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = - s.getConnections(); - for (int j = serviceConnections.size() - 1; j >= 0; j--) { - final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j); - for (int k = clist.size() - 1; k >= 0; k--) { - final ConnectionRecord cr = clist.get(k); - final ProcessRecord client = cr.binding.client; - containsCycle |= client.mState.isReversedReachable(); - if (client.mState.isReversedReachable()) { - continue; - } - queue.offer(client); - client.mState.setReversedReachable(true); - } - } - } - final ProcessProviderRecord ppr = pr.mProviders; - for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) { - final ContentProviderRecord cpr = ppr.getProviderAt(i); - for (int j = cpr.connections.size() - 1; j >= 0; j--) { - final ContentProviderConnection conn = cpr.connections.get(j); - final ProcessRecord client = conn.client; - containsCycle |= client.mState.isReversedReachable(); - if (client.mState.isReversedReachable()) { - continue; - } - queue.offer(client); - client.mState.setReversedReachable(true); - } - } - // If this process is a sandbox itself, also add the app on whose behalf - // its running - if (pr.isSdkSandbox) { - for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) { - ServiceRecord s = psr.getRunningServiceAt(is); - ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = - s.getConnections(); - for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) { - ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni); - for (int i = clist.size() - 1; i >= 0; i--) { - ConnectionRecord cr = clist.get(i); - ProcessRecord attributedApp = cr.binding.attributedClient; - if (attributedApp == null || attributedApp == pr) { - continue; - } - containsCycle |= attributedApp.mState.isReversedReachable(); - if (attributedApp.mState.isReversedReachable()) { - continue; - } - queue.offer(attributedApp); - attributedApp.mState.setReversedReachable(true); - } - } - } - } + clientProcesses.clear(); + mUnreachedProcessCollector.processList = clientProcesses; + for (int i = 0; i < appsSize; i++) { + final ProcessRecord app = apps.valueAt(i); + app.forEachClient(mUnreachedProcessCollector); } + mUnreachedProcessCollector.processList = null; // Reset the temporary bits. for (int i = clientProcesses.size() - 1; i >= 0; i--) { - clientProcesses.get(i).mState.setReversedReachable(false); + clientProcesses.get(i).mState.setReachable(false); } for (int i = 0, size = apps.size(); i < size; i++) { final ProcessRecord app = apps.valueAt(i); app.mState.setReachable(false); - app.mState.setReversedReachable(false); } - return containsCycle; } @GuardedBy({"mService", "mProcLock"}) @@ -917,10 +855,6 @@ public class OomAdjusterModernImpl extends OomAdjuster { final int procStateTarget = mProcessRecordProcStateNodes.size() - 1; final int adjTarget = mProcessRecordAdjNodes.size() - 1; - final int appUid = !fullUpdate && targetProcesses.size() > 0 - ? targetProcesses.valueAt(0).uid : -1; - final int logUid = mService.mCurOomAdjUid; - mAdjSeq++; // All apps to be updated will be moved to the lowest slot. if (fullUpdate) { @@ -974,7 +908,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { // We don't update the adj list since we're resetting it below. } - // Now nodes are set into their slots, without facting in the bindings. + // Now nodes are set into their slots, without factoring in the bindings. // The nodes between the `lastNode` pointer and the TAIL should be the new nodes. // // The whole rationale here is that, the bindings from client to host app, won't elevate diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 2c6e598ef0a4..b2082d9e8dc0 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -69,8 +69,10 @@ import com.android.server.wm.WindowProcessController; import com.android.server.wm.WindowProcessListener; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.Consumer; /** * Full information about a particular process that @@ -1613,4 +1615,50 @@ class ProcessRecord implements WindowProcessListener { public boolean wasForceStopped() { return mWasForceStopped; } + + /** + * Traverses all client processes and feed them to consumer. + */ + @GuardedBy("mProcLock") + void forEachClient(@NonNull Consumer<ProcessRecord> consumer) { + for (int i = mServices.numberOfRunningServices() - 1; i >= 0; i--) { + final ServiceRecord s = mServices.getRunningServiceAt(i); + final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = + s.getConnections(); + for (int j = serviceConnections.size() - 1; j >= 0; j--) { + final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j); + for (int k = clist.size() - 1; k >= 0; k--) { + final ConnectionRecord cr = clist.get(k); + consumer.accept(cr.binding.client); + } + } + } + for (int i = mProviders.numberOfProviders() - 1; i >= 0; i--) { + final ContentProviderRecord cpr = mProviders.getProviderAt(i); + for (int j = cpr.connections.size() - 1; j >= 0; j--) { + final ContentProviderConnection conn = cpr.connections.get(j); + consumer.accept(conn.client); + } + } + // If this process is a sandbox itself, also add the app on whose behalf + // its running + if (isSdkSandbox) { + for (int is = mServices.numberOfRunningServices() - 1; is >= 0; is--) { + ServiceRecord s = mServices.getRunningServiceAt(is); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = + s.getConnections(); + for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) { + ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni); + for (int i = clist.size() - 1; i >= 0; i--) { + ConnectionRecord cr = clist.get(i); + ProcessRecord attributedApp = cr.binding.attributedClient; + if (attributedApp == null || attributedApp == this) { + continue; + } + consumer.accept(attributedApp); + } + } + } + } + } } diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index 8723c5d549e4..5ad921fd0bae 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -379,12 +379,6 @@ final class ProcessStateRecord { private boolean mReachable; /** - * Whether or not this process is reversed reachable from given process. - */ - @GuardedBy("mService") - private boolean mReversedReachable; - - /** * The most recent time when the last visible activity within this process became invisible. * * <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is @@ -997,16 +991,6 @@ final class ProcessStateRecord { } @GuardedBy("mService") - boolean isReversedReachable() { - return mReversedReachable; - } - - @GuardedBy("mService") - void setReversedReachable(boolean reversedReachable) { - mReversedReachable = reversedReachable; - } - - @GuardedBy("mService") void resetCachedInfo() { mCachedHasActivities = VALUE_INVALID; mCachedIsHeavyWeight = VALUE_INVALID; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 9031cde89391..df106a794041 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -154,7 +154,6 @@ import android.media.permission.SafeCloseable; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.media.projection.IMediaProjectionManager; -import android.media.session.MediaSessionManager; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -312,9 +311,6 @@ public class AudioService extends IAudioService.Stub private final ContentResolver mContentResolver; private final AppOpsManager mAppOps; - /** do not use directly, use getMediaSessionManager() which handles lazy initialization */ - @Nullable private volatile MediaSessionManager mMediaSessionManager; - // the platform type affects volume and silent mode behavior private final int mPlatformType; @@ -945,8 +941,6 @@ public class AudioService extends IAudioService.Stub private final SoundDoseHelper mSoundDoseHelper; - private final HardeningEnforcer mHardeningEnforcer; - private final Object mSupportedSystemUsagesLock = new Object(); @GuardedBy("mSupportedSystemUsagesLock") private @AttributeSystemUsage int[] mSupportedSystemUsages = @@ -1321,8 +1315,6 @@ public class AudioService extends IAudioService.Stub mDisplayManager = context.getSystemService(DisplayManager.class); mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler); - - mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive()); } private void initVolumeStreamStates() { @@ -1394,6 +1386,7 @@ public class AudioService extends IAudioService.Stub // check on volume initialization checkVolumeRangeInitialization("AudioService()"); + } private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener = @@ -1406,14 +1399,6 @@ public class AudioService extends IAudioService.Stub } }; - private MediaSessionManager getMediaSessionManager() { - if (mMediaSessionManager == null) { - mMediaSessionManager = (MediaSessionManager) mContext - .getSystemService(Context.MEDIA_SESSION_SERVICE); - } - return mMediaSessionManager; - } - /** * Initialize intent receives and settings observers for this service. * Must be called after createStreamStates() as the handling of some events @@ -3442,10 +3427,6 @@ public class AudioService extends IAudioService.Stub * Part of service interface, check permissions here */ public void adjustStreamVolumeWithAttribution(int streamType, int direction, int flags, String callingPackage, String attributionTag) { - if (mHardeningEnforcer.blockVolumeMethod( - HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME)) { - return; - } if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) { Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without" + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); @@ -4222,10 +4203,6 @@ public class AudioService extends IAudioService.Stub * Part of service interface, check permissions here */ public void setStreamVolumeWithAttribution(int streamType, int index, int flags, String callingPackage, String attributionTag) { - if (mHardeningEnforcer.blockVolumeMethod( - HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME)) { - return; - } setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null, callingPackage, attributionTag); } @@ -4269,7 +4246,8 @@ public class AudioService extends IAudioService.Stub if (device == null) { // call was already logged in setDeviceVolume() sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, - index/*val1*/, flags/*val2*/, callingPackage)); + index/*val1*/, flags/*val2*/, getStreamVolume(streamType) /*val3*/, + callingPackage)); } setStreamVolume(streamType, index, flags, device, callingPackage, callingPackage, attributionTag, @@ -5079,7 +5057,6 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#setMasterMute(boolean, int) */ public void setMasterMute(boolean mute, int flags, String callingPackage, int userId, String attributionTag) { - super.setMasterMute_enforcePermission(); setMasterMuteInternal(mute, flags, callingPackage, @@ -5445,10 +5422,6 @@ public class AudioService extends IAudioService.Stub } public void setRingerModeExternal(int ringerMode, String caller) { - if (mHardeningEnforcer.blockVolumeMethod( - HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_RINGER_MODE)) { - return; - } if (isAndroidNPlus(caller) && wouldToggleZenMode(ringerMode) && !mNm.isNotificationPolicyAccessGrantedForPackage(caller)) { throw new SecurityException("Not allowed to change Do Not Disturb state"); @@ -6201,35 +6174,6 @@ public class AudioService extends IAudioService.Stub AudioDeviceVolumeManager.ADJUST_MODE_NORMAL); } - /** - * @see AudioManager#adjustVolume(int, int) - * This method is redirected from AudioManager to AudioService for API hardening rules - * enforcement then to MediaSession for implementation. - */ - @Override - public void adjustVolume(int direction, int flags) { - if (mHardeningEnforcer.blockVolumeMethod( - HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_VOLUME)) { - return; - } - getMediaSessionManager().dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE, - direction, flags); - } - - /** - * @see AudioManager#adjustSuggestedStreamVolume(int, int, int) - * This method is redirected from AudioManager to AudioService for API hardening rules - * enforcement then to MediaSession for implementation. - */ - @Override - public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) { - if (mHardeningEnforcer.blockVolumeMethod( - HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME)) { - return; - } - getMediaSessionManager().dispatchAdjustVolume(suggestedStreamType, direction, flags); - } - /** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */ @Override public void setStreamVolumeForUid(int streamType, int index, int flags, @@ -8784,7 +8728,7 @@ public class AudioService extends IAudioService.Stub mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS, mStreamVolumeAlias[mStreamType]); AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent( - mStreamType, mStreamVolumeAlias[mStreamType], index)); + mStreamType, mStreamVolumeAlias[mStreamType], index, oldIndex)); sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions); } } diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index 21a7d31cf691..f69b9f6523cc 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -151,11 +151,13 @@ public class AudioServiceEvents { final int mStreamType; final int mAliasStreamType; final int mIndex; + final int mOldIndex; - VolChangedBroadcastEvent(int stream, int alias, int index) { + VolChangedBroadcastEvent(int stream, int alias, int index, int oldIndex) { mStreamType = stream; mAliasStreamType = alias; mIndex = index; + mOldIndex = oldIndex; } @Override @@ -163,7 +165,8 @@ public class AudioServiceEvents { return new StringBuilder("sending VOLUME_CHANGED stream:") .append(AudioSystem.streamToString(mStreamType)) .append(" index:").append(mIndex) - .append(" alias:").append(AudioSystem.streamToString(mAliasStreamType)) + .append(" (was:").append(mOldIndex) + .append(") alias:").append(AudioSystem.streamToString(mAliasStreamType)) .toString(); } } @@ -234,19 +237,35 @@ public class AudioServiceEvents { final int mStream; final int mVal1; final int mVal2; + final int mVal3; final String mCaller; final String mGroupName; + /** used for VOL_SET_STREAM_VOL */ + VolumeEvent(int op, int stream, int val1, int val2, int val3, String caller) { + mOp = op; + mStream = stream; + mVal1 = val1; + mVal2 = val2; + mVal3 = val3; + mCaller = caller; + // unused + mGroupName = null; + logMetricEvent(); + } + /** used for VOL_ADJUST_VOL_UID, * VOL_ADJUST_SUGG_VOL, * VOL_ADJUST_STREAM_VOL, - * VOL_SET_STREAM_VOL */ + */ VolumeEvent(int op, int stream, int val1, int val2, String caller) { mOp = op; mStream = stream; mVal1 = val1; mVal2 = val2; mCaller = caller; + // unused + mVal3 = -1; mGroupName = null; logMetricEvent(); } @@ -257,6 +276,7 @@ public class AudioServiceEvents { mVal1 = index; mVal2 = gainDb; // unused + mVal3 = -1; mStream = -1; mCaller = null; mGroupName = null; @@ -269,6 +289,7 @@ public class AudioServiceEvents { mVal1 = index; // unused mVal2 = 0; + mVal3 = -1; mStream = -1; mCaller = null; mGroupName = null; @@ -282,6 +303,7 @@ public class AudioServiceEvents { mVal1 = index; mVal2 = voiceActive ? 1 : 0; // unused + mVal3 = -1; mCaller = null; mGroupName = null; logMetricEvent(); @@ -294,6 +316,7 @@ public class AudioServiceEvents { mVal1 = index; mVal2 = mode; // unused + mVal3 = -1; mCaller = null; mGroupName = null; logMetricEvent(); @@ -308,6 +331,8 @@ public class AudioServiceEvents { mVal2 = flags; mCaller = caller; mGroupName = group; + // unused + mVal3 = -1; logMetricEvent(); } @@ -317,8 +342,10 @@ public class AudioServiceEvents { mStream = stream; mVal1 = state ? 1 : 0; mVal2 = 0; + // unused mCaller = null; mGroupName = null; + mVal3 = -1; logMetricEvent(); } @@ -328,6 +355,8 @@ public class AudioServiceEvents { mStream = -1; mVal1 = state ? 1 : 0; mVal2 = 0; + // unused + mVal3 = -1; mCaller = null; mGroupName = null; logMetricEvent(); @@ -386,6 +415,7 @@ public class AudioServiceEvents { .set(MediaMetrics.Property.EVENT, "setStreamVolume") .set(MediaMetrics.Property.FLAGS, mVal2) .set(MediaMetrics.Property.INDEX, mVal1) + .set(MediaMetrics.Property.OLD_INDEX, mVal3) .set(MediaMetrics.Property.STREAM_TYPE, AudioSystem.streamToString(mStream)) .record(); @@ -478,6 +508,7 @@ public class AudioServiceEvents { .append(AudioSystem.streamToString(mStream)) .append(" index:").append(mVal1) .append(" flags:0x").append(Integer.toHexString(mVal2)) + .append(" oldIndex:").append(mVal3) .append(") from ").append(mCaller) .toString(); case VOL_SET_HEARING_AID_VOL: diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java deleted file mode 100644 index 4ceb83b2e1c9..000000000000 --- a/services/core/java/com/android/server/audio/HardeningEnforcer.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.server.audio; - -import static android.media.audio.Flags.autoPublicVolumeApiHardening; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.media.AudioManager; -import android.os.Binder; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.Log; - -/** - * Class to encapsulate all audio API hardening operations - */ -public class HardeningEnforcer { - - private static final String TAG = "AS.HardeningEnforcer"; - - final Context mContext; - final boolean mIsAutomotive; - - /** - * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)} - */ - public static final int METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME = 100; - /** - * Matches calls from {@link AudioManager#adjustVolume(int, int)} - */ - public static final int METHOD_AUDIO_MANAGER_ADJUST_VOLUME = 101; - /** - * Matches calls from {@link AudioManager#adjustSuggestedStreamVolume(int, int, int)} - */ - public static final int METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME = 102; - /** - * Matches calls from {@link AudioManager#adjustStreamVolume(int, int, int)} - */ - public static final int METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME = 103; - /** - * Matches calls from {@link AudioManager#setRingerMode(int)} - */ - public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200; - - public HardeningEnforcer(Context ctxt, boolean isAutomotive) { - mContext = ctxt; - mIsAutomotive = isAutomotive; - } - - /** - * Checks whether the call in the current thread should be allowed or blocked - * @param volumeMethod name of the method to check, for logging purposes - * @return false if the method call is allowed, true if it should be a no-op - */ - protected boolean blockVolumeMethod(int volumeMethod) { - // for Auto, volume methods require MODIFY_AUDIO_SETTINGS_PRIVILEGED - if (mIsAutomotive) { - if (!autoPublicVolumeApiHardening()) { - // automotive hardening flag disabled, no blocking on auto - return false; - } - if (mContext.checkCallingOrSelfPermission( - Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) - == PackageManager.PERMISSION_GRANTED) { - return false; - } - if (Binder.getCallingUid() < UserHandle.AID_APP_START) { - return false; - } - // TODO metrics? - // TODO log for audio dumpsys? - Log.e(TAG, "Preventing volume method " + volumeMethod + " for " - + getPackNameForUid(Binder.getCallingUid())); - return true; - } - // not blocking - return false; - } - - private String getPackNameForUid(int uid) { - final long token = Binder.clearCallingIdentity(); - try { - final String[] names = mContext.getPackageManager().getPackagesForUid(uid); - if (names == null - || names.length == 0 - || TextUtils.isEmpty(names[0])) { - return "[" + uid + "]"; - } - return names[0]; - } finally { - Binder.restoreCallingIdentity(token); - } - } -} diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index ea92154f2df0..61e4f36a34ed 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -19,6 +19,9 @@ package com.android.server.audio; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; import static android.media.AudioSystem.isBluetoothDevice; +import static android.media.AudioSystem.isBluetoothLeDevice; + +import static com.android.media.audio.Flags.dsaOverBtLeAudio; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1625,10 +1628,10 @@ public class SpatializerHelper { } private int getHeadSensorHandleUpdateTracker() { - int headHandle = -1; + Sensor htSensor = null; if (sRoutingDevices.isEmpty()) { logloge("getHeadSensorHandleUpdateTracker: no device, no head tracker"); - return headHandle; + return -1; } final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0); List<String> deviceAddresses = mAudioService.getDeviceAddresses(currentDevice); @@ -1642,27 +1645,86 @@ public class SpatializerHelper { for (String address : deviceAddresses) { UUID routingDeviceUuid = UuidUtils.uuidFromAudioDeviceAttributes( new AudioDeviceAttributes(currentDevice.getInternalType(), address)); - for (Sensor sensor : sensors) { - final UUID uuid = sensor.getUuid(); - if (uuid.equals(routingDeviceUuid)) { - headHandle = sensor.getHandle(); - if (!setHasHeadTracker(currentDevice)) { - headHandle = -1; + if (dsaOverBtLeAudio()) { + for (Sensor sensor : sensors) { + final UUID uuid = sensor.getUuid(); + if (uuid.equals(routingDeviceUuid)) { + htSensor = sensor; + HeadtrackerInfo info = new HeadtrackerInfo(sensor); + if (isBluetoothLeDevice(currentDevice.getInternalType())) { + if (info.getMajorVersion() == 2) { + // Version 2 is used only by LE Audio profile + break; + } + // we do not break, as this could be a match on the A2DP sensor + // for a dual mode headset. + } else if (info.getMajorVersion() == 1) { + // Version 1 is used only by A2DP profile + break; + } + } + if (htSensor == null && uuid.equals(UuidUtils.STANDALONE_UUID)) { + htSensor = sensor; + // we do not break, perhaps we find a head tracker on device. } - break; } - if (uuid.equals(UuidUtils.STANDALONE_UUID)) { - headHandle = sensor.getHandle(); - // we do not break, perhaps we find a head tracker on device. + if (htSensor != null) { + if (htSensor.getUuid().equals(UuidUtils.STANDALONE_UUID)) { + break; + } + if (setHasHeadTracker(currentDevice)) { + break; + } else { + htSensor = null; + } + } + } else { + for (Sensor sensor : sensors) { + final UUID uuid = sensor.getUuid(); + if (uuid.equals(routingDeviceUuid)) { + htSensor = sensor; + if (!setHasHeadTracker(currentDevice)) { + htSensor = null; + } + break; + } + if (uuid.equals(UuidUtils.STANDALONE_UUID)) { + htSensor = sensor; + // we do not break, perhaps we find a head tracker on device. + } + } + if (htSensor != null) { + break; } - } - if (headHandle != -1) { - break; } } - return headHandle; + return htSensor != null ? htSensor.getHandle() : -1; } + /** + * Contains the information parsed from the head tracker sensor version. + * See platform/hardware/libhardware/modules/sensors/dynamic_sensor/HidRawSensor.h + * for the definition of version and capability fields. + */ + private static class HeadtrackerInfo { + private final int mVersion; + HeadtrackerInfo(Sensor sensor) { + mVersion = sensor.getVersion(); + } + int getMajorVersion() { + return (mVersion & 0xFF000000) >> 24; + } + int getMinorVersion() { + return (mVersion & 0xFF0000) >> 16; + } + boolean hasAclTransport() { + return getMajorVersion() == 2 ? ((mVersion & 0x1) != 0) : false; + } + boolean hasIsoTransport() { + return getMajorVersion() == 2 ? ((mVersion & 0x2) != 0) : false; + } + }; + private int getScreenSensorHandle() { int screenHandle = -1; Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index b9ccbfbf55e7..c5073001a672 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -576,7 +576,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onDialogAnimatedIn(boolean startFingerprintNow) { - if (mState != STATE_AUTH_STARTED) { + if (mState != STATE_AUTH_STARTED && mState != STATE_ERROR_PENDING_SYSUI) { Slog.e(TAG, "onDialogAnimatedIn, unexpected state: " + mState); return; } 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 c629b2b91603..be78ea20d09d 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -157,4 +157,10 @@ public abstract class VirtualDeviceManagerInternal { * @see VirtualDevice#getPersistentDeviceId() */ public abstract @Nullable String getPersistentIdForDevice(int deviceId); + + /** + * Returns all current persistent device IDs, including the ones for which no virtual device + * exists, as long as one may have existed or can be created. + */ + public abstract @NonNull Set<String> getAllPersistentDeviceIds(); } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index fac727f17283..dff14b5fbdd0 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -1114,12 +1114,22 @@ public final class DeviceStateManagerService extends SystemService { public void notifyDeviceStateInfoAsync(@NonNull DeviceStateInfo info) { mHandler.post(() -> { + boolean tracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER); + if (tracingEnabled) { // To avoid creating the string when not needed. + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, + "notifyDeviceStateInfoAsync(pid=" + mPid + ")"); + } try { mCallback.onDeviceStateInfoChanged(info); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.", ex); } + finally { + if (tracingEnabled) { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + } }); } diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java index 669580189d7c..9fcaa1e2af16 100644 --- a/services/core/java/com/android/server/display/DisplayBrightnessState.java +++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java @@ -38,6 +38,7 @@ public final class DisplayBrightnessState { private final boolean mShouldUseAutoBrightness; private final boolean mIsSlowChange; + private final boolean mShouldUpdateScreenBrightnessSetting; private final float mCustomAnimationRate; @@ -50,6 +51,7 @@ public final class DisplayBrightnessState { mIsSlowChange = builder.isSlowChange(); mMaxBrightness = builder.getMaxBrightness(); mCustomAnimationRate = builder.getCustomAnimationRate(); + mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting(); } /** @@ -109,6 +111,13 @@ public final class DisplayBrightnessState { return mCustomAnimationRate; } + /** + * @return {@code true} if the screen brightness setting should be updated + */ + public boolean shouldUpdateScreenBrightnessSetting() { + return mShouldUpdateScreenBrightnessSetting; + } + @Override public String toString() { StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:"); @@ -123,6 +132,8 @@ public final class DisplayBrightnessState { stringBuilder.append("\n isSlowChange:").append(mIsSlowChange); stringBuilder.append("\n maxBrightness:").append(mMaxBrightness); stringBuilder.append("\n customAnimationRate:").append(mCustomAnimationRate); + stringBuilder.append("\n shouldUpdateScreenBrightnessSetting:") + .append(mShouldUpdateScreenBrightnessSetting); return stringBuilder.toString(); } @@ -149,13 +160,16 @@ public final class DisplayBrightnessState { && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness() && mIsSlowChange == otherState.isSlowChange() && mMaxBrightness == otherState.getMaxBrightness() - && mCustomAnimationRate == otherState.getCustomAnimationRate(); + && mCustomAnimationRate == otherState.getCustomAnimationRate() + && mShouldUpdateScreenBrightnessSetting + == otherState.shouldUpdateScreenBrightnessSetting(); } @Override public int hashCode() { return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason, - mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate); + mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate, + mShouldUpdateScreenBrightnessSetting); } /** @@ -177,6 +191,7 @@ public final class DisplayBrightnessState { private boolean mIsSlowChange; private float mMaxBrightness; private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET; + private boolean mShouldUpdateScreenBrightnessSetting; /** * Create a builder starting with the values from the specified {@link @@ -194,6 +209,8 @@ public final class DisplayBrightnessState { builder.setIsSlowChange(state.isSlowChange()); builder.setMaxBrightness(state.getMaxBrightness()); builder.setCustomAnimationRate(state.getCustomAnimationRate()); + builder.setShouldUpdateScreenBrightnessSetting( + state.shouldUpdateScreenBrightnessSetting()); return builder; } @@ -290,8 +307,8 @@ public final class DisplayBrightnessState { /** * See {@link DisplayBrightnessState#isSlowChange()}. */ - public Builder setIsSlowChange(boolean shouldUseAutoBrightness) { - this.mIsSlowChange = shouldUseAutoBrightness; + public Builder setIsSlowChange(boolean isSlowChange) { + this.mIsSlowChange = isSlowChange; return this; } @@ -334,6 +351,22 @@ public final class DisplayBrightnessState { } /** + * See {@link DisplayBrightnessState#shouldUpdateScreenBrightnessSetting()}. + */ + public boolean shouldUpdateScreenBrightnessSetting() { + return mShouldUpdateScreenBrightnessSetting; + } + + /** + * See {@link DisplayBrightnessState#shouldUpdateScreenBrightnessSetting()}. + */ + public Builder setShouldUpdateScreenBrightnessSetting( + boolean shouldUpdateScreenBrightnessSetting) { + mShouldUpdateScreenBrightnessSetting = shouldUpdateScreenBrightnessSetting; + return this; + } + + /** * This is used to construct an immutable DisplayBrightnessState object from its builder */ public DisplayBrightnessState build() { diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 9f4f78794659..2fdf90d7d286 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -209,7 +209,7 @@ abstract class DisplayDevice { int state, float brightnessState, float sdrBrightnessState, - @Nullable DisplayOffloadSession displayOffloadSession) { + @Nullable DisplayOffloadSessionImpl displayOffloadSession) { return null; } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 11f4e5f75b11..e99f82aee96d 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -4948,20 +4948,8 @@ public final class DisplayManagerService extends SystemService { return null; } - DisplayOffloadSession session = - new DisplayOffloadSession() { - @Override - public void setDozeStateOverride(int displayState) { - synchronized (mSyncRoot) { - displayPowerController.overrideDozeScreenState(displayState); - } - } - - @Override - public DisplayOffloader getDisplayOffloader() { - return displayOffloader; - } - }; + DisplayOffloadSessionImpl session = new DisplayOffloadSessionImpl(displayOffloader, + displayPowerController); logicalDisplay.setDisplayOffloadSessionLocked(session); displayPowerController.setDisplayOffloadSession(session); return session; diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java new file mode 100644 index 000000000000..1bd556bdcc4f --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java @@ -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. + */ + +package com.android.server.display; + +import android.annotation.Nullable; +import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; +import android.os.Trace; + +/** + * An implementation of the offload session that keeps track of whether the session is active. + * An offload session is used to control the display's brightness using the offload chip. + */ +public class DisplayOffloadSessionImpl implements DisplayManagerInternal.DisplayOffloadSession { + + @Nullable + private final DisplayManagerInternal.DisplayOffloader mDisplayOffloader; + private final DisplayPowerControllerInterface mDisplayPowerController; + private boolean mIsActive; + + public DisplayOffloadSessionImpl( + @Nullable DisplayManagerInternal.DisplayOffloader displayOffloader, + DisplayPowerControllerInterface displayPowerController) { + mDisplayOffloader = displayOffloader; + mDisplayPowerController = displayPowerController; + } + + @Override + public void setDozeStateOverride(int displayState) { + mDisplayPowerController.overrideDozeScreenState(displayState); + } + + @Override + public boolean isActive() { + return mIsActive; + } + + @Override + public void updateBrightness(float brightness) { + if (mIsActive) { + mDisplayPowerController.setBrightnessFromOffload(brightness); + } + } + + /** + * Start the offload session. The method returns if the session is already active. + * @return Whether the session was started successfully + */ + public boolean startOffload() { + if (mDisplayOffloader == null || mIsActive) { + return false; + } + Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload"); + try { + return mIsActive = mDisplayOffloader.startOffload(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + } + + /** + * Stop the offload session. The method returns if the session is not active. + */ + public void stopOffload() { + if (mDisplayOffloader == null || !mIsActive) { + return; + } + Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayOffloader#stopOffload"); + try { + mDisplayOffloader.stopOffload(); + mIsActive = false; + mDisplayPowerController.setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + } +} diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 5761c31b29bf..f3d761a7372b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -2224,6 +2224,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } @Override + public void setBrightnessFromOffload(float brightness) { + // The old DPC is no longer supported + } + + @Override public BrightnessInfo getBrightnessInfo() { synchronized (mCachedBrightnessInfo) { return new BrightnessInfo( diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index a6155da86f9a..d4e0cbb126ed 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -151,6 +151,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private static final int MSG_SET_DWBC_STRONG_MODE = 14; private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15; private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16; + private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17; @@ -562,7 +563,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal new DisplayBrightnessController(context, null, mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault, brightnessSetting, () -> postBrightnessChangeRunnable(), - new HandlerExecutor(mHandler)); + new HandlerExecutor(mHandler), flags); mBrightnessClamperController = mInjector.getBrightnessClamperController( mHandler, modeChangeCallback::run, @@ -1394,7 +1395,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED); - boolean updateScreenBrightnessSetting = false; + boolean updateScreenBrightnessSetting = + displayBrightnessState.shouldUpdateScreenBrightnessSetting(); float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness(); // Apply auto-brightness. int brightnessAdjustmentFlags = 0; @@ -1854,6 +1856,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } @Override + public void setBrightnessFromOffload(float brightness) { + Message msg = mHandler.obtainMessage(MSG_SET_BRIGHTNESS_FROM_OFFLOAD, + Float.floatToIntBits(brightness), 0 /*unused*/); + mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); + } + + @Override public BrightnessInfo getBrightnessInfo() { synchronized (mCachedBrightnessInfo) { return new BrightnessInfo( @@ -2886,6 +2895,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal case MSG_SET_DWBC_LOGGING_ENABLED: setDwbcLoggingEnabled(msg.arg1); break; + case MSG_SET_BRIGHTNESS_FROM_OFFLOAD: + mDisplayBrightnessController.setBrightnessFromOffload( + Float.intBitsToFloat(msg.arg1)); + updatePowerState(); + break; } } } diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java index 181386a93c71..72079a4c82fe 100644 --- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java +++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java @@ -22,6 +22,7 @@ import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; import java.io.PrintWriter; @@ -150,6 +151,14 @@ public interface DisplayPowerControllerInterface { void setTemporaryAutoBrightnessAdjustment(float adjustment); /** + * Sets temporary brightness from the offload chip until we get a brightness value from + * the light sensor. + * @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and + * {@link PowerManager.BRIGHTNESS_MAX}. Values outside of that range will be ignored. + */ + void setBrightnessFromOffload(float brightness); + + /** * Gets the screen brightness setting */ float getScreenBrightnessSetting(); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index be3207dfb4ee..ff9a1ab61b13 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -19,11 +19,11 @@ package com.android.server.display; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.Mode.INVALID_MODE_ID; +import android.annotation.Nullable; import android.app.ActivityThread; import android.content.Context; import android.content.res.Resources; import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; -import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.hardware.sidekick.SidekickInternal; import android.os.Build; import android.os.Handler; @@ -238,7 +238,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { private boolean mAllmRequested; private boolean mGameContentTypeRequested; private boolean mSidekickActive; - private boolean mDisplayOffloadActive; private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo; // The supported display modes according to SurfaceFlinger private SurfaceControl.DisplayMode[] mSfDisplayModes; @@ -765,7 +764,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { final int state, final float brightnessState, final float sdrBrightnessState, - DisplayOffloadSession displayOffloadSession) { + @Nullable DisplayOffloadSessionImpl displayOffloadSession) { // Assume that the brightness is off if the display is being turned off. assert state != Display.STATE_OFF @@ -832,25 +831,13 @@ final class LocalDisplayAdapter extends DisplayAdapter { + ", state=" + Display.stateToString(state) + ")"); } - DisplayOffloader displayOffloader = - displayOffloadSession == null - ? null - : displayOffloadSession.getDisplayOffloader(); - boolean isDisplayOffloadEnabled = mFlags.isDisplayOffloadEnabled(); // We must tell sidekick/displayoffload to stop controlling the display // before we can change its power mode, so do that first. if (isDisplayOffloadEnabled) { - if (mDisplayOffloadActive && displayOffloader != null) { - Trace.traceBegin(Trace.TRACE_TAG_POWER, - "DisplayOffloader#stopOffload"); - try { - displayOffloader.stopOffload(); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_POWER); - } - mDisplayOffloadActive = false; + if (displayOffloadSession != null) { + displayOffloadSession.stopOffload(); } } else { if (mSidekickActive) { @@ -881,16 +868,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { // have a sidekick/displayoffload available, tell it now that it can take // control. if (isDisplayOffloadEnabled) { - if (DisplayOffloadSession.isSupportedOffloadState(state) && - displayOffloader != null - && !mDisplayOffloadActive) { - Trace.traceBegin( - Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload"); - try { - mDisplayOffloadActive = displayOffloader.startOffload(); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_POWER); - } + if (DisplayOffloadSession.isSupportedOffloadState(state) + && displayOffloadSession != null) { + displayOffloadSession.startOffload(); } } else { if (Display.isSuspendedState(state) && state != Display.STATE_OFF diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 3d4209e0d6f3..ba321ae5d807 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -138,7 +138,7 @@ final class LogicalDisplay { private final Rect mTempDisplayRect = new Rect(); /** A session token that controls the offloading operations of this logical display. */ - private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; + private DisplayOffloadSessionImpl mDisplayOffloadSession; /** * Name of a display group to which the display is assigned. @@ -969,12 +969,11 @@ final class LogicalDisplay { return mDisplayGroupName; } - public void setDisplayOffloadSessionLocked( - DisplayManagerInternal.DisplayOffloadSession session) { + public void setDisplayOffloadSessionLocked(DisplayOffloadSessionImpl session) { mDisplayOffloadSession = session; } - public DisplayManagerInternal.DisplayOffloadSession getDisplayOffloadSessionLocked() { + public DisplayOffloadSessionImpl getDisplayOffloadSessionLocked() { return mDisplayOffloadSession; } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 90e32a685a34..edbd42465534 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -43,7 +43,6 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED; import android.annotation.Nullable; import android.content.Context; import android.graphics.Point; -import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; import android.media.projection.IMediaProjection; @@ -380,7 +379,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { @Override public Runnable requestDisplayStateLocked(int state, float brightnessState, - float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) { + float sdrBrightnessState, DisplayOffloadSessionImpl displayOffloadSession) { if (state != mDisplayState) { mDisplayState = state; if (state == Display.STATE_OFF) { diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java index d7ae2699ee2d..8fe5f213d766 100644 --- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java +++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java @@ -39,7 +39,8 @@ public final class BrightnessReason { public static final int REASON_BOOST = 8; public static final int REASON_SCREEN_OFF_BRIGHTNESS_SENSOR = 9; public static final int REASON_FOLLOWER = 10; - public static final int REASON_MAX = REASON_FOLLOWER; + public static final int REASON_OFFLOAD = 11; + public static final int REASON_MAX = REASON_OFFLOAD; public static final int MODIFIER_DIMMED = 0x1; public static final int MODIFIER_LOW_POWER = 0x2; @@ -196,6 +197,8 @@ public final class BrightnessReason { return "screen_off_brightness_sensor"; case REASON_FOLLOWER: return "follower"; + case REASON_OFFLOAD: + return "offload"; default: return Integer.toString(reason); } diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index d6f0098c13cb..617befbbd17d 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -31,6 +31,7 @@ import com.android.server.display.BrightnessSetting; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; @@ -104,7 +105,8 @@ public final class DisplayBrightnessController { */ public DisplayBrightnessController(Context context, Injector injector, int displayId, float defaultScreenBrightness, BrightnessSetting brightnessSetting, - Runnable onBrightnessChangeRunnable, HandlerExecutor brightnessChangeExecutor) { + Runnable onBrightnessChangeRunnable, HandlerExecutor brightnessChangeExecutor, + DisplayManagerFlags flags) { if (injector == null) { injector = new Injector(); } @@ -116,7 +118,7 @@ public final class DisplayBrightnessController { mCurrentScreenBrightness = getScreenBrightnessSetting(); mOnBrightnessChangeRunnable = onBrightnessChangeRunnable; mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context, - displayId); + displayId, flags); mBrightnessChangeExecutor = brightnessChangeExecutor; mPersistBrightnessNitsForDefaultDisplay = context.getResources().getBoolean( com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay); @@ -172,6 +174,18 @@ public final class DisplayBrightnessController { } /** + * Sets the brightness from the offload session. + */ + public void setBrightnessFromOffload(float brightness) { + synchronized (mLock) { + if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null) { + mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() + .setOffloadScreenBrightness(brightness); + } + } + } + + /** * Returns a boolean flag indicating if the light sensor is to be used to decide the screen * brightness when dozing */ @@ -423,8 +437,9 @@ public final class DisplayBrightnessController { @VisibleForTesting static class Injector { DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context, - int displayId) { - return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId); + int displayId, DisplayManagerFlags flags) { + return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId, + flags); } } diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index f141c20158cd..055f94a23363 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -17,6 +17,7 @@ package com.android.server.display.brightness; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.hardware.display.DisplayManagerInternal; import android.util.IndentingPrintWriter; @@ -31,9 +32,11 @@ import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; import com.android.server.display.brightness.strategy.DozeBrightnessStrategy; import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy; import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy; +import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy; import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy; import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; @@ -63,6 +66,10 @@ public class DisplayBrightnessStrategySelector { private final InvalidBrightnessStrategy mInvalidBrightnessStrategy; // Controls brightness when automatic (adaptive) brightness is running. private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy; + // Controls the brightness if adaptive brightness is on and there exists an active offload + // session. Brightness value is provided by the offload session. + @Nullable + private final OffloadBrightnessStrategy mOffloadBrightnessStrategy; // We take note of the old brightness strategy so that we can know when the strategy changes. private String mOldBrightnessStrategyName; @@ -72,7 +79,8 @@ public class DisplayBrightnessStrategySelector { /** * The constructor of DozeBrightnessStrategy. */ - public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId) { + public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId, + DisplayManagerFlags flags) { if (injector == null) { injector = new Injector(); } @@ -85,6 +93,11 @@ public class DisplayBrightnessStrategySelector { mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId); mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy(); mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId); + if (flags.isDisplayOffloadEnabled()) { + mOffloadBrightnessStrategy = injector.getOffloadBrightnessStrategy(); + } else { + mOffloadBrightnessStrategy = null; + } mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean( R.bool.config_allowAutoBrightnessWhileDozing); mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName(); @@ -114,6 +127,9 @@ public class DisplayBrightnessStrategySelector { } else if (BrightnessUtils.isValidBrightnessValue( mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) { displayBrightnessStrategy = mTemporaryBrightnessStrategy; + } else if (mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue( + mOffloadBrightnessStrategy.getOffloadScreenBrightness())) { + displayBrightnessStrategy = mOffloadBrightnessStrategy; } if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) { @@ -138,6 +154,11 @@ public class DisplayBrightnessStrategySelector { return mAutomaticBrightnessStrategy; } + @Nullable + public OffloadBrightnessStrategy getOffloadBrightnessStrategy() { + return mOffloadBrightnessStrategy; + } + /** * Returns a boolean flag indicating if the light sensor is to be used to decide the screen * brightness when dozing @@ -159,6 +180,9 @@ public class DisplayBrightnessStrategySelector { + mAllowAutoBrightnessWhileDozingConfig); IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " "); mTemporaryBrightnessStrategy.dump(ipw); + if (mOffloadBrightnessStrategy != null) { + mOffloadBrightnessStrategy.dump(ipw); + } } /** @@ -210,5 +234,9 @@ public class DisplayBrightnessStrategySelector { AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) { return new AutomaticBrightnessStrategy(context, displayId); } + + OffloadBrightnessStrategy getOffloadBrightnessStrategy() { + return new OffloadBrightnessStrategy(); + } } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java index bcd52598edd2..3c23b5c10671 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java @@ -107,6 +107,7 @@ public class AutomaticBrightnessStrategy { mIsAutoBrightnessEnabled = shouldUseAutoBrightness() && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze) && brightnessReason != BrightnessReason.REASON_OVERRIDE + && brightnessReason != BrightnessReason.REASON_OFFLOAD && mAutomaticBrightnessController != null; mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness() && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze); diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java new file mode 100644 index 000000000000..55f8914e26f6 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java @@ -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.server.display.brightness.strategy; + +import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +import java.io.PrintWriter; + +/** + * Manages the brightness of the display when auto-brightness is on, the screen has just turned on + * and there is no available lux reading yet. The brightness value is read from the offload chip. + */ +public class OffloadBrightnessStrategy implements DisplayBrightnessStrategy { + + private float mOffloadScreenBrightness; + + public OffloadBrightnessStrategy() { + mOffloadScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + } + + @Override + public DisplayBrightnessState updateBrightness( + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) { + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_OFFLOAD); + return new DisplayBrightnessState.Builder() + .setBrightness(mOffloadScreenBrightness) + .setSdrBrightness(mOffloadScreenBrightness) + .setBrightnessReason(brightnessReason) + .setDisplayBrightnessStrategyName(getName()) + .setIsSlowChange(false) + .setShouldUpdateScreenBrightnessSetting(true) + .build(); + } + + @Override + public String getName() { + return "OffloadBrightnessStrategy"; + } + + public float getOffloadScreenBrightness() { + return mOffloadScreenBrightness; + } + + public void setOffloadScreenBrightness(float offloadScreenBrightness) { + mOffloadScreenBrightness = offloadScreenBrightness; + } + + /** + * Dumps the state of this class. + */ + public void dump(PrintWriter writer) { + writer.println("OffloadBrightnessStrategy:"); + writer.println(" mOffloadScreenBrightness:" + mOffloadScreenBrightness); + } +} diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java index 360a6a721988..6bdfae2dc02f 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -110,7 +110,7 @@ import java.util.Objects; @Override @NonNull - public synchronized MediaRoute2Info getDeviceRoute() { + public synchronized MediaRoute2Info getSelectedRoute() { if (mSelectedRoute != null) { return mSelectedRoute; } diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java index 7876095a548a..0fdaaa7604e5 100644 --- a/services/core/java/com/android/server/media/DeviceRouteController.java +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -72,13 +72,9 @@ import com.android.media.flags.Flags; */ boolean selectRoute(@Nullable @MediaRoute2Info.Type Integer type); - /** - * Returns currently selected device (built-in or wired) route. - * - * @return non-null device route. - */ + /** Returns the currently selected device (built-in or wired) route. */ @NonNull - MediaRoute2Info getDeviceRoute(); + MediaRoute2Info getSelectedRoute(); /** * Updates device route volume. diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java index 6ba40ae33f3c..65874e23dcdc 100644 --- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java +++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java @@ -107,7 +107,7 @@ import java.util.Objects; @Override @NonNull - public synchronized MediaRoute2Info getDeviceRoute() { + public synchronized MediaRoute2Info getSelectedRoute() { return mDeviceRoute; } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index a158b18d91b4..994d3ca1124f 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -76,6 +76,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; +import android.util.Slog; import android.view.KeyEvent; import com.android.server.LocalServices; @@ -348,16 +349,19 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } else { if (mVolumeControlType == VOLUME_CONTROL_FIXED) { if (DEBUG) { - Log.d(TAG, "Session does not support volume adjustment"); + Slog.d(TAG, "Session does not support volume adjustment"); } } else if (direction == AudioManager.ADJUST_TOGGLE_MUTE || direction == AudioManager.ADJUST_MUTE || direction == AudioManager.ADJUST_UNMUTE) { - Log.w(TAG, "Muting remote playback is not supported"); + Slog.w(TAG, "Muting remote playback is not supported"); } else { if (DEBUG) { - Log.w(TAG, "adjusting volume, pkg=" + packageName + ", asSystemService=" - + asSystemService + ", dir=" + direction); + Slog.w( + TAG, + "adjusting volume, pkg=" + packageName + + ", asSystemService=" + asSystemService + + ", dir=" + direction); } mSessionCb.adjustVolume(packageName, pid, uid, asSystemService, direction); @@ -371,8 +375,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } if (DEBUG) { - Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is " - + mMaxVolume); + Slog.d( + TAG, + "Adjusted optimistic volume to " + mOptimisticVolume + + " max is " + mMaxVolume); } } // Always notify, even if the volume hasn't changed. This is important to ensure that @@ -388,23 +394,33 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (mVolumeType == PLAYBACK_TYPE_LOCAL) { int stream = getVolumeStream(mAudioAttrs); final int volumeValue = value; - mHandler.post(new Runnable() { - @Override - public void run() { - try { - mAudioManager.setStreamVolumeForUid(stream, volumeValue, flags, - opPackageName, uid, pid, - mContext.getApplicationInfo().targetSdkVersion); - } catch (IllegalArgumentException | SecurityException e) { - Log.e(TAG, "Cannot set volume: stream=" + stream + ", value=" + volumeValue - + ", flags=" + flags, e); - } - } - }); + mHandler.post( + new Runnable() { + @Override + public void run() { + try { + mAudioManager.setStreamVolumeForUid( + stream, + volumeValue, + flags, + opPackageName, + uid, + pid, + mContext.getApplicationInfo().targetSdkVersion); + } catch (IllegalArgumentException | SecurityException e) { + Slog.e( + TAG, + "Cannot set volume: stream=" + stream + + ", value=" + volumeValue + + ", flags=" + flags, + e); + } + } + }); } else { if (mVolumeControlType != VOLUME_CONTROL_ABSOLUTE) { if (DEBUG) { - Log.d(TAG, "Session does not support setting volume"); + Slog.d(TAG, "Session does not support setting volume"); } } else { value = Math.max(0, Math.min(value, mMaxVolume)); @@ -419,8 +435,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } if (DEBUG) { - Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is " - + mMaxVolume); + Slog.d( + TAG, + "Set optimistic volume to " + mOptimisticVolume + + " max is " + mMaxVolume); } } // Always notify, even if the volume hasn't changed. @@ -527,12 +545,27 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @Override public boolean canHandleVolumeKey() { if (isPlaybackTypeLocal()) { + if (DEBUG) { + Slog.d(TAG, "Local MediaSessionRecord can handle volume key"); + } return true; } if (mVolumeControlType == VOLUME_CONTROL_FIXED) { + if (DEBUG) { + Slog.d( + TAG, + "Local MediaSessionRecord with FIXED volume control can't handle volume" + + " key"); + } return false; } if (mVolumeAdjustmentForRemoteGroupSessions) { + if (DEBUG) { + Slog.d( + TAG, + "Volume adjustment for remote group sessions allowed so MediaSessionRecord" + + " can handle volume key"); + } return true; } // See b/228021646 for details. @@ -540,7 +573,18 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR List<RoutingSessionInfo> sessions = mRouter2Manager.getRoutingSessions(mPackageName); boolean foundNonSystemSession = false; boolean remoteSessionAllowVolumeAdjustment = true; + if (DEBUG) { + Slog.d( + TAG, + "Found " + + sessions.size() + + " routing sessions for package name " + + mPackageName); + } for (RoutingSessionInfo session : sessions) { + if (DEBUG) { + Slog.d(TAG, "Found routingSessionInfo: " + session); + } if (!session.isSystemSession()) { foundNonSystemSession = true; if (session.getVolumeHandling() == PLAYBACK_VOLUME_FIXED) { @@ -549,9 +593,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } } if (!foundNonSystemSession) { - Log.d(TAG, "Package " + mPackageName - + " has a remote media session but no associated routing session"); + if (DEBUG) { + Slog.d( + TAG, + "Package " + mPackageName + + " has a remote media session but no associated routing session"); + } } + return foundNonSystemSession && remoteSessionAllowVolumeAdjustment; } @@ -637,8 +686,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR final boolean asSystemService, final boolean useSuggested, final int previousFlagPlaySound) { if (DEBUG) { - Log.w(TAG, "adjusting local volume, stream=" + stream + ", dir=" + direction - + ", asSystemService=" + asSystemService + ", useSuggested=" + useSuggested); + Slog.w( + TAG, + "adjusting local volume, stream=" + stream + ", dir=" + direction + + ", asSystemService=" + asSystemService + + ", useSuggested=" + useSuggested); } // Must use opPackageName for adjusting volumes with UID. final String opPackageName; @@ -653,40 +705,61 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR uid = callingUid; pid = callingPid; } - mHandler.post(new Runnable() { - @Override - public void run() { - try { - if (useSuggested) { - if (AudioSystem.isStreamActive(stream, 0)) { - mAudioManager.adjustSuggestedStreamVolumeForUid(stream, - direction, flags, opPackageName, uid, pid, - mContext.getApplicationInfo().targetSdkVersion); - } else { - mAudioManager.adjustSuggestedStreamVolumeForUid( - AudioManager.USE_DEFAULT_STREAM_TYPE, direction, - flags | previousFlagPlaySound, opPackageName, uid, pid, - mContext.getApplicationInfo().targetSdkVersion); + mHandler.post( + new Runnable() { + @Override + public void run() { + try { + if (useSuggested) { + if (AudioSystem.isStreamActive(stream, 0)) { + mAudioManager.adjustSuggestedStreamVolumeForUid( + stream, + direction, + flags, + opPackageName, + uid, + pid, + mContext.getApplicationInfo().targetSdkVersion); + } else { + mAudioManager.adjustSuggestedStreamVolumeForUid( + AudioManager.USE_DEFAULT_STREAM_TYPE, + direction, + flags | previousFlagPlaySound, + opPackageName, + uid, + pid, + mContext.getApplicationInfo().targetSdkVersion); + } + } else { + mAudioManager.adjustStreamVolumeForUid( + stream, + direction, + flags, + opPackageName, + uid, + pid, + mContext.getApplicationInfo().targetSdkVersion); + } + } catch (IllegalArgumentException | SecurityException e) { + Slog.e( + TAG, + "Cannot adjust volume: direction=" + direction + + ", stream=" + stream + ", flags=" + flags + + ", opPackageName=" + opPackageName + ", uid=" + uid + + ", useSuggested=" + useSuggested + + ", previousFlagPlaySound=" + previousFlagPlaySound, + e); } - } else { - mAudioManager.adjustStreamVolumeForUid(stream, direction, flags, - opPackageName, uid, pid, - mContext.getApplicationInfo().targetSdkVersion); } - } catch (IllegalArgumentException | SecurityException e) { - Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream=" - + stream + ", flags=" + flags + ", opPackageName=" + opPackageName - + ", uid=" + uid + ", useSuggested=" + useSuggested - + ", previousFlagPlaySound=" + previousFlagPlaySound, e); - } - } - }); + }); } private void logCallbackException( String msg, ISessionControllerCallbackHolder holder, Exception e) { - Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName - + ", exception=" + e); + Slog.v( + TAG, + msg + ", this=" + this + ", callback package=" + holder.mPackageName + + ", exception=" + e); } private void pushPlaybackStateUpdate() { @@ -1083,7 +1156,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR throw new IllegalArgumentException( "The media button receiver cannot be set to an activity."); } else { - Log.w(TAG, "Ignoring invalid media button receiver targeting an activity."); + Slog.w( + TAG, + "Ignoring invalid media button receiver targeting an activity."); return; } } @@ -1119,7 +1194,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (CompatChanges.isChangeEnabled(THROW_FOR_INVALID_BROADCAST_RECEIVER, uid)) { throw new IllegalArgumentException("Invalid component name: " + receiver); } else { - Log.w( + Slog.w( TAG, "setMediaButtonBroadcastReceiver(): " + "Ignoring invalid component name=" @@ -1258,7 +1333,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (attributes != null) { mAudioAttrs = attributes; } else { - Log.e(TAG, "Received null audio attributes, using existing attributes"); + Slog.e(TAG, "Received null audio attributes, using existing attributes"); } } if (typeChanged) { @@ -1320,7 +1395,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } return true; } catch (RemoteException e) { - Log.e(TAG, "Remote failure in sendMediaRequest.", e); + Slog.e(TAG, "Remote failure in sendMediaRequest.", e); } return false; } @@ -1343,7 +1418,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } return true; } catch (RemoteException e) { - Log.e(TAG, "Remote failure in sendMediaRequest.", e); + Slog.e(TAG, "Remote failure in sendMediaRequest.", e); } return false; } @@ -1356,7 +1431,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onCommand(packageName, pid, uid, command, args, cb); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in sendCommand.", e); + Slog.e(TAG, "Remote failure in sendCommand.", e); } } @@ -1368,7 +1443,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onCustomAction(packageName, pid, uid, action, args); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in sendCustomAction.", e); + Slog.e(TAG, "Remote failure in sendCustomAction.", e); } } @@ -1379,7 +1454,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPrepare(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in prepare.", e); + Slog.e(TAG, "Remote failure in prepare.", e); } } @@ -1391,7 +1466,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPrepareFromMediaId(packageName, pid, uid, mediaId, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in prepareFromMediaId.", e); + Slog.e(TAG, "Remote failure in prepareFromMediaId.", e); } } @@ -1403,7 +1478,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPrepareFromSearch(packageName, pid, uid, query, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in prepareFromSearch.", e); + Slog.e(TAG, "Remote failure in prepareFromSearch.", e); } } @@ -1414,7 +1489,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPrepareFromUri(packageName, pid, uid, uri, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in prepareFromUri.", e); + Slog.e(TAG, "Remote failure in prepareFromUri.", e); } } @@ -1425,7 +1500,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPlay(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in play.", e); + Slog.e(TAG, "Remote failure in play.", e); } } @@ -1437,7 +1512,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPlayFromMediaId(packageName, pid, uid, mediaId, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in playFromMediaId.", e); + Slog.e(TAG, "Remote failure in playFromMediaId.", e); } } @@ -1449,7 +1524,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPlayFromSearch(packageName, pid, uid, query, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in playFromSearch.", e); + Slog.e(TAG, "Remote failure in playFromSearch.", e); } } @@ -1460,7 +1535,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPlayFromUri(packageName, pid, uid, uri, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in playFromUri.", e); + Slog.e(TAG, "Remote failure in playFromUri.", e); } } @@ -1471,7 +1546,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onSkipToTrack(packageName, pid, uid, id); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in skipToTrack", e); + Slog.e(TAG, "Remote failure in skipToTrack", e); } } @@ -1482,7 +1557,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPause(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in pause.", e); + Slog.e(TAG, "Remote failure in pause.", e); } } @@ -1493,7 +1568,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onStop(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in stop.", e); + Slog.e(TAG, "Remote failure in stop.", e); } } @@ -1504,7 +1579,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onNext(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in next.", e); + Slog.e(TAG, "Remote failure in next.", e); } } @@ -1515,7 +1590,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPrevious(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in previous.", e); + Slog.e(TAG, "Remote failure in previous.", e); } } @@ -1526,7 +1601,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onFastForward(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in fastForward.", e); + Slog.e(TAG, "Remote failure in fastForward.", e); } } @@ -1537,7 +1612,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onRewind(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in rewind.", e); + Slog.e(TAG, "Remote failure in rewind.", e); } } @@ -1548,7 +1623,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onSeekTo(packageName, pid, uid, pos); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in seekTo.", e); + Slog.e(TAG, "Remote failure in seekTo.", e); } } @@ -1559,7 +1634,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onRate(packageName, pid, uid, rating); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in rate.", e); + Slog.e(TAG, "Remote failure in rate.", e); } } @@ -1570,7 +1645,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onSetPlaybackSpeed(packageName, pid, uid, speed); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in setPlaybackSpeed.", e); + Slog.e(TAG, "Remote failure in setPlaybackSpeed.", e); } } @@ -1587,7 +1662,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR mCb.onAdjustVolume(packageName, pid, uid, direction); } } catch (RemoteException e) { - Log.e(TAG, "Remote failure in adjustVolume.", e); + Slog.e(TAG, "Remote failure in adjustVolume.", e); } } @@ -1598,7 +1673,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onSetVolumeTo(packageName, pid, uid, value); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in setVolumeTo.", e); + Slog.e(TAG, "Remote failure in setVolumeTo.", e); } } @@ -1641,8 +1716,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR cb, packageName, Binder.getCallingUid(), () -> unregisterCallback(cb)); mControllerCallbackHolders.add(holder); if (DEBUG) { - Log.d(TAG, "registering controller callback " + cb + " from controller" - + packageName); + Slog.d( + TAG, + "registering controller callback " + cb + + " from controller" + packageName); } // Avoid callback leaks try { @@ -1651,7 +1728,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR cb.asBinder().linkToDeath(holder.mDeathMonitor, 0); } catch (RemoteException e) { unregisterCallback(cb); - Log.w(TAG, "registerCallback failed to linkToDeath", e); + Slog.w(TAG, "registerCallback failed to linkToDeath", e); } } } @@ -1666,12 +1743,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR cb.asBinder().unlinkToDeath( mControllerCallbackHolders.get(index).mDeathMonitor, 0); } catch (NoSuchElementException e) { - Log.w(TAG, "error unlinking to binder death", e); + Slog.w(TAG, "error unlinking to binder death", e); } mControllerCallbackHolders.remove(index); } if (DEBUG) { - Log.d(TAG, "unregistering callback " + cb.asBinder()); + Slog.d(TAG, "unregistering callback " + cb.asBinder()); } } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 78077a831622..c8dba800a017 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -228,8 +228,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { return; } - MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); - if (TextUtils.equals(routeId, deviceRoute.getId())) { + MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); + if (TextUtils.equals(routeId, selectedDeviceRoute.getId())) { mBluetoothRouteController.transferTo(null); } else { mBluetoothRouteController.transferTo(routeId); @@ -278,11 +278,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { return null; } - MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); + MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( SYSTEM_SESSION_ID, packageName).setSystemSession(true); - builder.addSelectedRoute(deviceRoute.getId()); + builder.addSelectedRoute(selectedDeviceRoute.getId()); for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) { builder.addTransferableRoute(route.getId()); } @@ -314,7 +314,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); // We must have a device route in the provider info. - builder.addRoute(mDeviceRouteController.getDeviceRoute()); + builder.addRoute(mDeviceRouteController.getSelectedRoute()); for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) { builder.addRoute(route); @@ -338,12 +338,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { SYSTEM_SESSION_ID, "" /* clientPackageName */) .setSystemSession(true); - MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); - MediaRoute2Info selectedRoute = deviceRoute; + MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); + MediaRoute2Info selectedRoute = selectedDeviceRoute; MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute(); if (selectedBtRoute != null) { selectedRoute = selectedBtRoute; - builder.addTransferableRoute(deviceRoute.getId()); + builder.addTransferableRoute(selectedDeviceRoute.getId()); } mSelectedRouteId = selectedRoute.getId(); mDefaultRoute = diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bae06347d8a2..c2b59644ce1c 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -184,6 +184,7 @@ import android.companion.AssociationRequest; import android.companion.ICompanionDeviceManager; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; +import android.compat.annotation.EnabledSince; import android.compat.annotation.LoggingOnly; import android.content.AttributionSource; import android.content.BroadcastReceiver; @@ -555,7 +556,7 @@ public class NotificationManagerService extends SystemService { * creation and activation of an implicit {@link android.app.AutomaticZenRule}. */ @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) static final long MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES = 308670109L; private static final Duration POST_WAKE_LOCK_TIMEOUT = Duration.ofSeconds(30); @@ -828,6 +829,22 @@ public class NotificationManagerService extends SystemService { } } + // Removes all notifications with the specified user & package. + public void removePackageNotifications(String pkg, @UserIdInt int userId) { + synchronized (mBufferLock) { + Iterator<Pair<StatusBarNotification, Integer>> bufferIter = descendingIterator(); + while (bufferIter.hasNext()) { + final Pair<StatusBarNotification, Integer> pair = bufferIter.next(); + if (pair.first != null + && userId == pair.first.getNormalizedUserId() + && pkg != null && pkg.equals(pair.first.getPackageName()) + && pair.first.getNotification() != null) { + bufferIter.remove(); + } + } + } + } + void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter) { synchronized (mBufferLock) { Iterator<Pair<StatusBarNotification, Integer>> iter = descendingIterator(); @@ -1902,7 +1919,6 @@ public class NotificationManagerService extends SystemService { unhideNotificationsForPackages(pkgList, uidList); } } - mHandler.scheduleOnPackageChanged(removingPackage, changeUserId, pkgList, uidList); } } @@ -4021,11 +4037,8 @@ public class NotificationManagerService extends SystemService { Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e); return false; } - final boolean showStickyHunIfDenied = mFlagResolver.isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags - .SHOW_STICKY_HUN_FOR_DENIED_FSI); return checkUseFullScreenIntentPermission(attributionSource, applicationInfo, - showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */); + false /* forDataDelivery */); } @Override @@ -4219,7 +4232,8 @@ public class NotificationManagerService extends SystemService { boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel( pkg, callingUid, channelId, callingUid, isSystemOrSystemUi); if (previouslyExisted) { - // Remove from both recent notification archive and notification history + // Remove from both recent notification archive (recently dismissed notifications) + // and notification history mArchive.removeChannelNotifications(pkg, callingUser, channelId); mHistoryManager.deleteNotificationChannel(pkg, callingUid, channelId); mListeners.notifyNotificationChannelChanged(pkg, @@ -7274,28 +7288,12 @@ public class NotificationManagerService extends SystemService { notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED; if (notification.fullScreenIntent != null) { - final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE); - if (forceDemoteFsiToStickyHun) { + final AttributionSource attributionSource = + new AttributionSource.Builder(notificationUid).setPackageName(pkg).build(); + final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission( + attributionSource, ai, true /* forDataDelivery */); + if (!canUseFullScreenIntent) { makeStickyHun(notification, pkg, userId); - } else { - final AttributionSource attributionSource = - new AttributionSource.Builder(notificationUid).setPackageName(pkg).build(); - final boolean showStickyHunIfDenied = mFlagResolver.isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags - .SHOW_STICKY_HUN_FOR_DENIED_FSI); - final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission( - attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */, - true /* forDataDelivery */); - if (!canUseFullScreenIntent) { - if (showStickyHunIfDenied) { - makeStickyHun(notification, pkg, userId); - } else { - notification.fullScreenIntent = null; - Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the" - + "USE_FULL_SCREEN_INTENT permission"); - } - } } } @@ -7402,27 +7400,20 @@ public class NotificationManagerService extends SystemService { } private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource, - @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission, + @NonNull ApplicationInfo applicationInfo, boolean forDataDelivery) { if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) { return true; } - if (isAppOpPermission) { - final int permissionResult; - if (forDataDelivery) { - permissionResult = mPermissionManager.checkPermissionForDataDelivery( - permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null); - } else { - permissionResult = mPermissionManager.checkPermissionForPreflight( - permission.USE_FULL_SCREEN_INTENT, attributionSource); - } - return permissionResult == PermissionManager.PERMISSION_GRANTED; + final int permissionResult; + if (forDataDelivery) { + permissionResult = mPermissionManager.checkPermissionForDataDelivery( + permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null); } else { - final int permissionResult = getContext().checkPermission( - permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(), - attributionSource.getUid()); - return permissionResult == PERMISSION_GRANTED; + permissionResult = mPermissionManager.checkPermissionForPreflight( + permission.USE_FULL_SCREEN_INTENT, attributionSource); } + return permissionResult == PermissionManager.PERMISSION_GRANTED; } private void checkRemoteViews(String pkg, String tag, int id, Notification notification) { @@ -9444,7 +9435,11 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < size; i++) { final String pkg = pkgList[i]; final int uid = uidList[i]; - mHistoryManager.onPackageRemoved(UserHandle.getUserId(uid), pkg); + final int userHandle = UserHandle.getUserId(uid); + // Removes this package's notifications from both recent notification archive + // (recently dismissed notifications) and notification history. + mArchive.removePackageNotifications(pkg, userHandle); + mHistoryManager.onPackageRemoved(userHandle, pkg); } } if (preferencesChanged) { diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index d2e980b7e355..9a6ea2c2aeb8 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -530,16 +530,13 @@ interface NotificationRecordLogger { this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter(); this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r); - final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver() - .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI); - final boolean hasFullScreenIntent = p.r.getSbn().getNotification().fullScreenIntent != null; final boolean hasFsiRequestedButDeniedFlag = (p.r.getSbn().getNotification().flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0; - this.fsi_state = NotificationRecordLogger.getFsiState(isStickyHunFlagEnabled, + this.fsi_state = NotificationRecordLogger.getFsiState( hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType); this.is_locked = p.r.isLocked(); @@ -587,13 +584,10 @@ interface NotificationRecordLogger { * @return FrameworkStatsLog enum of the state of the full screen intent posted with this * notification. */ - static int getFsiState(boolean isStickyHunFlagEnabled, - boolean hasFullScreenIntent, + static int getFsiState(boolean hasFullScreenIntent, boolean hasFsiRequestedButDeniedFlag, NotificationReportedEvent eventType) { - - if (!isStickyHunFlagEnabled - || eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) { + if (eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) { // Zeroes in protos take zero bandwidth, but non-zero numbers take bandwidth, // so we should log 0 when possible. return 0; diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 783e9bbb034f..252664a7e4e4 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -2143,10 +2143,7 @@ public class PreferencesHelper implements RankingConfig { * @return State of the full screen intent permission for this package. */ @VisibleForTesting - int getFsiState(String pkg, int uid, boolean requestedFSIPermission, boolean isFlagEnabled) { - if (!isFlagEnabled) { - return 0; - } + int getFsiState(String pkg, int uid, boolean requestedFSIPermission) { if (!requestedFSIPermission) { return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; } @@ -2167,10 +2164,8 @@ public class PreferencesHelper implements RankingConfig { * the user. */ @VisibleForTesting - boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags, - boolean isStickyHunFlagEnabled) { - if (!isStickyHunFlagEnabled - || fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) { + boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags) { + if (fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) { return false; } return (currentPermissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0; @@ -2213,22 +2208,18 @@ public class PreferencesHelper implements RankingConfig { pkgsWithPermissionsToHandle.remove(key); } - final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver() - .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI); - final boolean requestedFSIPermission = mPermissionHelper.hasRequestedPermission( android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, r.uid); - final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission, - isStickyHunFlagEnabled); + final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission); final int currentPermissionFlags = mPm.getPermissionFlags( android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, UserHandle.getUserHandleForUid(r.uid)); final boolean fsiIsUserSet = - isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags, - isStickyHunFlagEnabled); + isFsiPermissionUserSet(r.pkg, r.uid, fsiState, + currentPermissionFlags); events.add(FrameworkStatsLog.buildStatsEvent( PACKAGE_NOTIFICATION_PREFERENCES, diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index 79cd2a0b236f..92d469ccbfac 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -259,6 +259,19 @@ public interface Computer extends PackageDataSnapshot { */ boolean shouldFilterApplicationIncludingUninstalled(@Nullable PackageStateInternal ps, int callingUid, int userId); + + /** + * Different from + * {@link #shouldFilterApplicationIncludingUninstalled(PackageStateInternal, int, int)}, the + * function returns {@code true} if: + * <ul> + * <li>The target package is not archived. + * <li>The package cannot be found in the device or has been uninstalled in the current user. + * </ul> + */ + boolean shouldFilterApplicationIncludingUninstalledNotArchived( + @Nullable PackageStateInternal ps, + int callingUid, int userId); /** * Different from {@link #shouldFilterApplication(SharedUserSetting, int, int)}, the function * returns {@code true} if packages with the same shared user are all uninstalled in the current diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 11a6d1b8f9a4..e5c4ccc73bc3 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -2455,7 +2455,8 @@ public class ComputerEngine implements Computer { */ public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps, int callingUid, @Nullable ComponentName component, - @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) { + @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall, + boolean filterArchived) { if (Process.isSdkSandboxUid(callingUid)) { int clientAppUid = Process.getAppUidForSdkSandboxUid(callingUid); // SDK sandbox should be able to see it's client app @@ -2469,14 +2470,20 @@ public class ComputerEngine implements Computer { } final String instantAppPkgName = getInstantAppPackageName(callingUid); final boolean callerIsInstantApp = instantAppPkgName != null; + final boolean packageArchivedForUser = ps != null && PackageArchiver.isArchived( + ps.getUserStateOrDefault(userId)); // Don't treat hiddenUntilInstalled as an uninstalled state, phone app needs to access // these hidden application details to customize carrier apps. Also, allowing the system // caller accessing to application across users. if (ps == null || (filterUninstall - && !isSystemOrRootOrShell(callingUid) - && !ps.isHiddenUntilInstalled() - && !ps.getUserStateOrDefault(userId).isInstalled())) { + && !isSystemOrRootOrShell(callingUid) + && !ps.isHiddenUntilInstalled() + && !ps.getUserStateOrDefault(userId).isInstalled() + // Archived packages behave like uninstalled packages. So if filterUninstall is + // set to true, we dismiss filtering some uninstalled package only if it is + // archived and filterArchived is set as false. + && (!packageArchivedForUser || filterArchived))) { // If caller is instant app or sdk sandbox and ps is null, pretend the application // exists, but, needs to be filtered return (callerIsInstantApp || filterUninstall || Process.isSdkSandboxUid(callingUid)); @@ -2524,7 +2531,20 @@ public class ComputerEngine implements Computer { } /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean) + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) + */ + public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps, + int callingUid, @Nullable ComponentName component, + @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) { + return shouldFilterApplication( + ps, callingUid, component, componentType, userId, filterUninstall, + true /* filterArchived */); + } + + /** + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) */ public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps, int callingUid, @Nullable ComponentName component, @@ -2534,7 +2554,8 @@ public class ComputerEngine implements Computer { } /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean) + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) */ public final boolean shouldFilterApplication( @Nullable PackageStateInternal ps, int callingUid, int userId) { @@ -2543,7 +2564,8 @@ public class ComputerEngine implements Computer { } /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean) + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) */ public final boolean shouldFilterApplication(@NonNull SharedUserSetting sus, int callingUid, int userId) { @@ -2558,7 +2580,8 @@ public class ComputerEngine implements Computer { } /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean) + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) */ public final boolean shouldFilterApplicationIncludingUninstalled( @Nullable PackageStateInternal ps, int callingUid, int userId) { @@ -2567,7 +2590,19 @@ public class ComputerEngine implements Computer { } /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean) + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) + */ + public final boolean shouldFilterApplicationIncludingUninstalledNotArchived( + @Nullable PackageStateInternal ps, int callingUid, int userId) { + return shouldFilterApplication( + ps, callingUid, null, TYPE_UNKNOWN, userId, true /* filterUninstall */, + false /* filterArchived */); + } + + /** + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean, + * boolean) */ public final boolean shouldFilterApplicationIncludingUninstalled( @NonNull SharedUserSetting sus, int callingUid, int userId) { @@ -5015,7 +5050,7 @@ public class ComputerEngine implements Computer { String installerPackageName = installSource.mInstallerPackageName; if (installerPackageName != null) { final PackageStateInternal ps = mSettings.getPackage(installerPackageName); - if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, + if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid, UserHandle.getUserId(callingUid))) { installerPackageName = null; } @@ -5033,7 +5068,8 @@ public class ComputerEngine implements Computer { return InstallSource.EMPTY; } - if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) { + if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid, + userId)) { return null; } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 65c6329587a5..3b3d79e7dee1 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -440,7 +440,7 @@ final class DeletePackageHelper { if (outInfo != null) { // Remember which users are affected, before the installed states are modified outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL) - ? ps.queryInstalledUsers(allUserHandles, /* installed= */true) + ? ps.queryUsersInstalledOrHasData(allUserHandles) : new int[]{userId}; } diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index eff6157380c0..968be5c2cf1c 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -37,6 +37,7 @@ import android.app.BroadcastOptions; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.pm.ApplicationInfo; import android.content.pm.ArchivedActivityParcel; import android.content.pm.ArchivedPackageParcel; import android.content.pm.LauncherActivityInfo; @@ -94,6 +95,9 @@ public class PackageArchiver { private static final String TAG = "PackageArchiverService"; + public static final String EXTRA_UNARCHIVE_INTENT_SENDER = + "android.content.pm.extra.UNARCHIVE_INTENT_SENDER"; + /** * The maximum time granted for an app store to start a foreground service when unarchival * is requested. @@ -103,6 +107,8 @@ public class PackageArchiver { private static final String ARCHIVE_ICONS_DIR = "package_archiver"; + private static final String ACTION_UNARCHIVE_DIALOG = "android.intent.action.UNARCHIVE_DIALOG"; + private final Context mContext; private final PackageManagerService mPm; @@ -140,6 +146,8 @@ public class PackageArchiver { } snapshot.enforceCrossUserPermission(binderUid, userId, true, true, "archiveApp"); + verifyUninstallPermissions(); + CompletableFuture<ArchiveState> archiveStateFuture; try { archiveStateFuture = createArchiveState(packageName, userId); @@ -182,6 +190,7 @@ public class PackageArchiver { throws PackageManager.NameNotFoundException { PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(), Binder.getCallingUid(), userId); + verifyNotSystemApp(ps.getFlags()); String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); verifyInstaller(responsibleInstallerPackage, userId); verifyOptOutStatus(packageName, @@ -316,6 +325,13 @@ public class PackageArchiver { return intentReceivers != null && !intentReceivers.getList().isEmpty(); } + private void verifyNotSystemApp(int flags) throws PackageManager.NameNotFoundException { + if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 || ( + (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) { + throw new PackageManager.NameNotFoundException("System apps cannot be archived."); + } + } + /** * Returns true if the app is archivable. */ @@ -337,6 +353,11 @@ public class PackageArchiver { throw new ParcelableException(e); } + if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0 || ( + (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) { + return false; + } + if (isAppOptedOutOfArchiving(packageName, ps.getAppId())) { return false; } @@ -372,9 +393,11 @@ public class PackageArchiver { void requestUnarchive( @NonNull String packageName, @NonNull String callerPackageName, + @NonNull IntentSender statusReceiver, @NonNull UserHandle userHandle) { Objects.requireNonNull(packageName); Objects.requireNonNull(callerPackageName); + Objects.requireNonNull(statusReceiver); Objects.requireNonNull(userHandle); Computer snapshot = mPm.snapshotComputer(); @@ -385,9 +408,12 @@ public class PackageArchiver { } snapshot.enforceCrossUserPermission(binderUid, userId, true, true, "unarchiveApp"); + PackageStateInternal ps; + PackageStateInternal callerPs; try { ps = getPackageState(packageName, snapshot, binderUid, userId); + callerPs = getPackageState(callerPackageName, snapshot, binderUid, userId); verifyArchived(ps, userId); } catch (PackageManager.NameNotFoundException e) { throw new ParcelableException(e); @@ -400,9 +426,32 @@ public class PackageArchiver { packageName))); } + boolean hasInstallPackages = mContext.checkCallingOrSelfPermission( + Manifest.permission.INSTALL_PACKAGES) + == PackageManager.PERMISSION_GRANTED; + // We don't check the AppOpsManager here for REQUEST_INSTALL_PACKAGES because the requester + // is not the source of the installation. + boolean hasRequestInstallPackages = callerPs.getAndroidPackage().getRequestedPermissions() + .contains(android.Manifest.permission.REQUEST_INSTALL_PACKAGES); + if (!hasInstallPackages && !hasRequestInstallPackages) { + throw new SecurityException("You need the com.android.permission.INSTALL_PACKAGES " + + "or com.android.permission.REQUEST_INSTALL_PACKAGES permission to request " + + "an unarchival."); + } + + if (!hasInstallPackages) { + requestUnarchiveConfirmation(packageName, statusReceiver); + return; + } + + // TODO(b/311709794) Check that the responsible installer has INSTALL_PACKAGES or + // OPSTR_REQUEST_INSTALL_PACKAGES too. Edge case: In reality this should always be the case, + // unless a user has disabled the permission after archiving an app. + int draftSessionId; try { - draftSessionId = createDraftSession(packageName, installerPackage, userId); + draftSessionId = Binder.withCleanCallingIdentity(() -> + createDraftSession(packageName, installerPackage, statusReceiver, userId)); } catch (RuntimeException e) { if (e.getCause() instanceof IOException) { throw ExceptionUtils.wrap((IOException) e.getCause()); @@ -415,11 +464,38 @@ public class PackageArchiver { () -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId)); } - private int createDraftSession(String packageName, String installerPackage, int userId) { + private void requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver) { + final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_DIALOG); + dialogIntent.putExtra(EXTRA_UNARCHIVE_INTENT_SENDER, statusReceiver); + dialogIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); + + final Intent broadcastIntent = new Intent(); + broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); + broadcastIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, + PackageInstaller.STATUS_PENDING_USER_ACTION); + broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent); + sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent); + } + + private void verifyUninstallPermissions() { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES) + != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission( + Manifest.permission.REQUEST_DELETE_PACKAGES) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("You need the com.android.permission.DELETE_PACKAGES " + + "or com.android.permission.REQUEST_DELETE_PACKAGES permission to request " + + "an archival."); + } + } + + private int createDraftSession(String packageName, String installerPackage, + IntentSender statusReceiver, int userId) throws IOException { PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); sessionParams.setAppPackageName(packageName); sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT; + sessionParams.unarchiveIntentSender = statusReceiver; + int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId); // Handles case of repeated unarchival calls for the same package. int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid, @@ -429,12 +505,11 @@ public class PackageArchiver { return existingSessionId; } - int sessionId = Binder.withCleanCallingIdentity( - () -> mPm.mInstallerService.createSessionInternal( - sessionParams, - installerPackage, mContext.getAttributionTag(), - installerUid, - userId)); + int sessionId = mPm.mInstallerService.createSessionInternal( + sessionParams, + installerPackage, mContext.getAttributionTag(), + installerUid, + userId); // TODO(b/297358628) Also cleanup sessions upon device restart. mPm.mHandler.postDelayed(() -> mPm.mInstallerService.cleanupDraftIfUnclaimed(sessionId), getUnarchiveForegroundTimeout()); @@ -644,20 +719,25 @@ public class PackageArchiver { String message) { Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", packageName, message)); - final Intent fillIn = new Intent(); - fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); - fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); - fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, message); + final Intent intent = new Intent(); + intent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); + intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, message); + sendIntent(statusReceiver, packageName, message, intent); + } + + private void sendIntent(IntentSender statusReceiver, String packageName, String message, + Intent intent) { try { final BroadcastOptions options = BroadcastOptions.makeBasic(); options.setPendingIntentBackgroundActivityStartMode( MODE_BACKGROUND_ACTIVITY_START_DENIED); - statusReceiver.sendIntent(mContext, 0, fillIn, /* onFinished= */ null, + statusReceiver.sendIntent(mContext, 0, intent, /* onFinished= */ null, /* handler= */ null, /* requiredPermission= */ null, options.toBundle()); } catch (IntentSender.SendIntentException e) { Slog.e( TAG, - TextUtils.formatSimple("Failed to send failure status for %s with message %s", + TextUtils.formatSimple("Failed to send status for %s with message %s", packageName, message), e); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index af43a8bec832..c9663fc30ef5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -16,8 +16,14 @@ package com.android.server.pm; +import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; +import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE; +import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY; +import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED; +import static android.content.pm.PackageInstaller.UNARCHIVAL_GENERIC_ERROR; +import static android.content.pm.PackageInstaller.UNARCHIVAL_OK; import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; @@ -37,6 +43,7 @@ import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.PackageDeleteObserver; +import android.app.PendingIntent; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; @@ -56,6 +63,7 @@ import android.content.pm.PackageInstaller.InstallConstraints; import android.content.pm.PackageInstaller.InstallConstraintsResult; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageInstaller.UnarchivalStatus; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; @@ -71,6 +79,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.ParcelableException; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteCallbackList; @@ -1630,8 +1639,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements public void requestUnarchive( @NonNull String packageName, @NonNull String callerPackageName, + @NonNull IntentSender statusReceiver, @NonNull UserHandle userHandle) { - mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle); + mPackageArchiver.requestUnarchive(packageName, callerPackageName, statusReceiver, + userHandle); } @Override @@ -1689,6 +1700,102 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + // TODO(b/307299702) Implement error dialog and propagate userActionIntent. + @Override + public void reportUnarchivalStatus( + int unarchiveId, + @UnarchivalStatus int status, + long requiredStorageBytes, + @Nullable PendingIntent userActionIntent, + @NonNull UserHandle userHandle) { + verifyReportUnarchiveStatusInput( + status, requiredStorageBytes, userActionIntent, userHandle); + + int userId = userHandle.getIdentifier(); + int binderUid = Binder.getCallingUid(); + + synchronized (mSessions) { + PackageInstallerSession session = mSessions.get(unarchiveId); + if (session == null || session.userId != userId + || session.params.appPackageName == null) { + throw new ParcelableException(new PackageManager.NameNotFoundException( + TextUtils.formatSimple( + "No valid session with unarchival ID %s found for user %s.", + unarchiveId, userId))); + } + + if (!isCallingUidOwner(session)) { + throw new SecurityException(TextUtils.formatSimple( + "The caller UID %s does not have access to the session with unarchiveId " + + "%d.", + binderUid, unarchiveId)); + } + + IntentSender unarchiveIntentSender = session.params.unarchiveIntentSender; + if (unarchiveIntentSender == null) { + throw new IllegalStateException( + TextUtils.formatSimple( + "Unarchival status for ID %s has already been set or a " + + "session has been created for it already by the " + + "caller.", + unarchiveId)); + } + + // Execute expensive calls outside the sync block. + mPm.mHandler.post( + () -> notifyUnarchivalListener(status, session.params.appPackageName, + unarchiveIntentSender)); + session.params.unarchiveIntentSender = null; + if (status != UNARCHIVAL_OK) { + Binder.withCleanCallingIdentity(session::abandon); + } + } + } + + private static void verifyReportUnarchiveStatusInput(int status, long requiredStorageBytes, + @Nullable PendingIntent userActionIntent, + @NonNull UserHandle userHandle) { + Objects.requireNonNull(userHandle); + if (status == UNARCHIVAL_ERROR_USER_ACTION_NEEDED) { + Objects.requireNonNull(userActionIntent); + } + if (status == UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes <= 0) { + throw new IllegalStateException( + "Insufficient storage error set, but requiredStorageBytes unspecified."); + } + if (status != UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes > 0) { + throw new IllegalStateException( + TextUtils.formatSimple("requiredStorageBytes set, but error is %s.", status) + ); + } + if (!List.of( + UNARCHIVAL_OK, + UNARCHIVAL_ERROR_USER_ACTION_NEEDED, + UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE, + UNARCHIVAL_ERROR_NO_CONNECTIVITY, + UNARCHIVAL_GENERIC_ERROR).contains(status)) { + throw new IllegalStateException("Invalid status code passed " + status); + } + } + + private void notifyUnarchivalListener(int status, String packageName, + IntentSender unarchiveIntentSender) { + final Intent fillIn = new Intent(); + fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); + fillIn.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, status); + // TODO(b/307299702) Attach failure dialog with EXTRA_INTENT and requiredStorageBytes here. + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_DENIED); + try { + unarchiveIntentSender.sendIntent(mContext, 0, fillIn, /* onFinished= */ null, + /* handler= */ null, /* requiredPermission= */ null, + options.toBundle()); + } catch (SendIntentException e) { + Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e); + } + } + private static int getSessionCount(SparseArray<PackageInstallerSession> sessions, int installerUid) { int count = 0; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d38c0a0920b1..47d1df5df1c0 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -3586,21 +3586,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.setDontKillApp(false); } + boolean existingSplitReplacedOrRemoved = false; // Inherit splits if not overridden. if (!ArrayUtils.isEmpty(existing.getSplitNames())) { for (int i = 0; i < existing.getSplitNames().length; i++) { final String splitName = existing.getSplitNames()[i]; final File splitFile = new File(existing.getSplitApkPaths()[i]); final boolean splitRemoved = removeSplitList.contains(splitName); - if (!stagedSplits.contains(splitName) && !splitRemoved) { + final boolean splitReplaced = stagedSplits.contains(splitName); + if (!splitReplaced && !splitRemoved) { inheritFileLocked(splitFile); // Collect the requiredSplitTypes and staged splitTypes from splits CollectionUtils.addAll(requiredSplitTypes, existing.getRequiredSplitTypes()[i]); CollectionUtils.addAll(stagedSplitTypes, existing.getSplitTypes()[i]); + } else { + existingSplitReplacedOrRemoved = true; } } } + if (existingSplitReplacedOrRemoved + && (params.installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) { + // Some splits are being replaced or removed. Make sure the app is restarted. + params.setDontKillApp(false); + } // Inherit compiled oat directory. final File packageInstallDir = (new File(appInfo.getBaseCodePath())).getParentFile(); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 6f45d2befc6b..fc662038d5d5 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -4689,10 +4689,12 @@ class PackageManagerShellCommand extends ShellCommand { final int translatedUserId = translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive"); + final LocalIntentReceiver receiver = new LocalIntentReceiver(); try { mInterface.getPackageInstaller().requestUnarchive(packageName, - /* callerPackageName= */ "", new UserHandle(translatedUserId)); + mContext.getPackageName(), receiver.getIntentSender(), + new UserHandle(translatedUserId)); } catch (Exception e) { pw.println("Failure [" + e.getMessage() + "]"); return 1; diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 72090f24a2ed..b50d0a07aa3a 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -779,6 +779,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return readUserState(userId).isInstalled(); } + boolean isArchived(int userId) { + return PackageArchiver.isArchived(readUserState(userId)); + } + int getInstallReason(int userId) { return readUserState(userId).getInstallReason(); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 4e14c908b01b..85563172cf05 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1592,6 +1592,19 @@ public class UserManagerService extends IUserManager.Stub { */ private void showConfirmCredentialToDisableQuietMode( @UserIdInt int userId, @Nullable IntentSender target) { + if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) { + // TODO (b/308121702) It may be brittle to rely on user states to check profile state + int state; + synchronized (mUserStates) { + state = mUserStates.get(userId, UserState.STATE_NONE); + } + if (state != UserState.STATE_NONE) { + Slog.i(LOG_TAG, + "showConfirmCredentialToDisableQuietMode() called too early, user " + userId + + " is still alive."); + return; + } + } // otherwise, we show a profile challenge to trigger decryption of the user final KeyguardManager km = (KeyguardManager) mContext.getSystemService( Context.KEYGUARD_SERVICE); diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index ee3b74653b75..dd39fb02573e 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -32,8 +32,6 @@ import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; -import android.os.WorkDuration; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseIntArray; @@ -197,9 +195,6 @@ public final class HintManagerService extends SystemService { private static native void nativeSetMode(long halPtr, int mode, boolean enabled); - private static native void nativeReportActualWorkDuration(long halPtr, - WorkDuration[] workDurations); - /** Wrapper for HintManager.nativeInit */ public void halInit() { nativeInit(); @@ -257,10 +252,6 @@ public final class HintManagerService extends SystemService { nativeSetMode(halPtr, mode, enabled); } - /** Wrapper for HintManager.nativeReportActualWorkDuration */ - public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) { - nativeReportActualWorkDuration(halPtr, workDurations); - } } @VisibleForTesting @@ -633,52 +624,6 @@ public final class HintManagerService extends SystemService { } } - @Override - public void reportActualWorkDuration2(WorkDuration[] workDurations) { - synchronized (this) { - if (mHalSessionPtr == 0 || !mUpdateAllowed) { - return; - } - Preconditions.checkArgument(workDurations.length != 0, "the count" - + " of work durations shouldn't be 0."); - for (WorkDuration workDuration : workDurations) { - validateWorkDuration(workDuration); - } - mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations); - } - } - - void validateWorkDuration(WorkDuration workDuration) { - if (DEBUG) { - Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", " - + workDuration.getWorkPeriodStartTimestampNanos() + ", " - + workDuration.getActualTotalDurationNanos() + ", " - + workDuration.getActualCpuDurationNanos() + ", " - + workDuration.getActualGpuDurationNanos() + ")"); - } - if (workDuration.getWorkPeriodStartTimestampNanos() <= 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple( - "Work period start timestamp (%d) should be greater than 0", - workDuration.getWorkPeriodStartTimestampNanos())); - } - if (workDuration.getActualTotalDurationNanos() <= 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple("Actual total duration (%d) should be greater than 0", - workDuration.getActualTotalDurationNanos())); - } - if (workDuration.getActualCpuDurationNanos() <= 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0", - workDuration.getActualCpuDurationNanos())); - } - if (workDuration.getActualGpuDurationNanos() < 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple("Actual GPU duration (%d) should be non negative", - workDuration.getActualGpuDurationNanos())); - } - } - private void onProcStateChanged(boolean updateAllowed) { updateHintAllowed(updateAllowed); } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 17499bb77239..940feb580a96 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -24,12 +24,14 @@ import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED; import static android.hardware.graphics.common.Hdr.DOLBY_VISION; +import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkStats.METERED_YES; import static android.net.NetworkTemplate.MATCH_ETHERNET; import static android.net.NetworkTemplate.MATCH_MOBILE; +import static android.net.NetworkTemplate.MATCH_PROXY; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.OEM_MANAGED_ALL; import static android.net.NetworkTemplate.OEM_MANAGED_PAID; @@ -488,6 +490,7 @@ public class StatsPullAtomService extends SystemService { case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: + case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER: case FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER: @@ -973,21 +976,33 @@ public class StatsPullAtomService extends SystemService { if (DEBUG) { Slog.d(TAG, "Registering NetworkStats pullers with statsd"); } + + boolean canQueryTypeProxy = canQueryNetworkStatsForTypeProxy(); + // Initialize NetworkStats baselines. - mNetworkStatsBaselines.addAll( - collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER)); - mNetworkStatsBaselines.addAll( - collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG)); - mNetworkStatsBaselines.addAll( - collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER)); - mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( - FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG)); - mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( - FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED)); - mNetworkStatsBaselines.addAll( - collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER)); - mNetworkStatsBaselines.addAll( - collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER)); + synchronized (mDataBytesTransferLock) { + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER)); + mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG)); + mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER)); + if (canQueryTypeProxy) { + mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG)); + } + } // Listen to subscription changes to record historical subscriptions that activated before // pulling, this is used by {@code DATA_USAGE_BYTES_TRANSFER}. @@ -1001,6 +1016,9 @@ public class StatsPullAtomService extends SystemService { registerBytesTransferByTagAndMetered(); registerDataUsageBytesTransfer(); registerOemManagedBytesTransfer(); + if (canQueryTypeProxy) { + registerProxyBytesTransferBackground(); + } } private void initAndRegisterDeferredPullers() { @@ -1171,6 +1189,18 @@ public class StatsPullAtomService extends SystemService { } break; } + case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: { + final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate( + new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/true); + if (stats != null) { + ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats), + new int[]{TRANSPORT_BLUETOOTH}, + /*slicedByFgbg=*/true, /*slicedByTag=*/false, + /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN, + /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/true)); + } + break; + } case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: { final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplate( new NetworkTemplate.Builder(MATCH_WIFI).build(), /*includeTags=*/true); @@ -1183,7 +1213,7 @@ public class StatsPullAtomService extends SystemService { new int[]{TRANSPORT_WIFI, TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false, /*slicedByTag=*/true, /*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN, - /*subInfo=*/null, OEM_MANAGED_ALL)); + /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/false)); } break; } @@ -1225,7 +1255,7 @@ public class StatsPullAtomService extends SystemService { final NetworkStatsExt diff = new NetworkStatsExt( removeEmptyEntries(item.stats.subtract(baseline.stats)), item.transports, item.slicedByFgbg, item.slicedByTag, item.slicedByMetered, item.ratType, - item.subInfo, item.oemManaged); + item.subInfo, item.oemManaged, item.isTypeProxy); // If no diff, skip. if (!diff.stats.iterator().hasNext()) continue; @@ -1363,7 +1393,7 @@ public class StatsPullAtomService extends SystemService { ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats), new int[]{transport}, /*slicedByFgbg=*/true, /*slicedByTag=*/false, /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN, - /*subInfo=*/null, oemManaged)); + /*subInfo=*/null, oemManaged, /*isTypeProxy=*/false)); } } } @@ -1392,6 +1422,21 @@ public class StatsPullAtomService extends SystemService { } /** + * Check if it is possible to query NetworkStats for TYPE_PROXY. This should only be possible + * if the build includes r.android.com/2828315 + * @return true if querying for TYPE_PROXY is allowed + */ + private static boolean canQueryNetworkStatsForTypeProxy() { + try { + new NetworkTemplate.Builder(MATCH_PROXY).build(); + return true; + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Querying network stats for TYPE_PROXY is not allowed"); + return false; + } + } + + /** * Create a snapshot of NetworkStats since boot for the given template, but add 1 bucket * duration before boot as a buffer to ensure at least one full bucket will be included. * Note that this should be only used to calculate diff since the snapshot might contains @@ -1450,7 +1495,7 @@ public class StatsPullAtomService extends SystemService { ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats), new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true, /*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo, - OEM_MANAGED_ALL)); + OEM_MANAGED_ALL, /*isTypeProxy=*/false)); } } return ret; @@ -1600,6 +1645,19 @@ public class StatsPullAtomService extends SystemService { ); } + private void registerProxyBytesTransferBackground() { + int tagId = FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG; + PullAtomMetadata metadata = new PullAtomMetadata.Builder() + .setAdditiveFields(new int[]{3, 4, 5, 6}) + .build(); + mStatsManager.setPullAtomCallback( + tagId, + metadata, + DIRECT_EXECUTOR, + mStatsCallbackImpl + ); + } + private void registerBytesTransferByTagAndMetered() { int tagId = FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED; PullAtomMetadata metadata = new PullAtomMetadata.Builder() diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java index 7dbba0d4337d..512f0bf7ff98 100644 --- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java +++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java @@ -42,15 +42,17 @@ public class NetworkStatsExt { public final int oemManaged; @Nullable public final SubInfo subInfo; + public final boolean isTypeProxy; // True if matching ConnectivityManager#TYPE_PROXY public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg) { this(stats, transports, slicedByFgbg, /*slicedByTag=*/false, /*slicedByMetered=*/false, - TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null, OEM_MANAGED_ALL); + TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null, + OEM_MANAGED_ALL, /*isTypeProxy=*/false); } public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg, boolean slicedByTag, boolean slicedByMetered, int ratType, - @Nullable SubInfo subInfo, int oemManaged) { + @Nullable SubInfo subInfo, int oemManaged, boolean isTypeProxy) { this.stats = stats; // Sort transports array so that we can test for equality without considering order. @@ -63,6 +65,7 @@ public class NetworkStatsExt { this.ratType = ratType; this.subInfo = subInfo; this.oemManaged = oemManaged; + this.isTypeProxy = isTypeProxy; } /** @@ -72,6 +75,6 @@ public class NetworkStatsExt { return Arrays.equals(transports, other.transports) && slicedByFgbg == other.slicedByFgbg && slicedByTag == other.slicedByTag && slicedByMetered == other.slicedByMetered && ratType == other.ratType && Objects.equals(subInfo, other.subInfo) - && oemManaged == other.oemManaged; + && oemManaged == other.oemManaged && isTypeProxy == other.isTypeProxy; } } diff --git a/services/core/java/com/android/server/timedetector/TEST_MAPPING b/services/core/java/com/android/server/timedetector/TEST_MAPPING index 5c37680af745..17d327e94d4d 100644 --- a/services/core/java/com/android/server/timedetector/TEST_MAPPING +++ b/services/core/java/com/android/server/timedetector/TEST_MAPPING @@ -7,10 +7,7 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] - } - ], - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + }, { "name": "FrameworksTimeServicesTests" } diff --git a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING index 63dd7b42f23b..358618a71cbc 100644 --- a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING +++ b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING @@ -7,15 +7,15 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "FrameworksTimeServicesTests" } ], // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. "postsubmit": [ { "name": "CtsLocationTimeZoneManagerHostTest" - }, - { - "name": "FrameworksTimeServicesTests" } ] } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index ee46ce13ee73..b3672ecb194c 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -16,6 +16,8 @@ package com.android.server.webkit; +import static android.webkit.Flags.updateServiceV2; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -51,7 +53,7 @@ public class WebViewUpdateService extends SystemService { private static final String TAG = "WebViewUpdateService"; private BroadcastReceiver mWebViewUpdatedReceiver; - private WebViewUpdateServiceImpl mImpl; + private WebViewUpdateServiceInterface mImpl; static final int PACKAGE_CHANGED = 0; static final int PACKAGE_ADDED = 1; @@ -60,7 +62,11 @@ public class WebViewUpdateService extends SystemService { public WebViewUpdateService(Context context) { super(context); - mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance()); + if (updateServiceV2()) { + mImpl = new WebViewUpdateServiceImpl2(context, SystemImpl.getInstance()); + } else { + mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance()); + } } @Override diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index 43d62aaa120a..cfdef1471f83 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -63,7 +63,7 @@ import java.util.List; * * @hide */ -class WebViewUpdateServiceImpl { +class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName(); private static class WebViewPackageMissingException extends Exception { @@ -112,7 +112,8 @@ class WebViewUpdateServiceImpl { mSystemInterface = systemInterface; } - void packageStateChanged(String packageName, int changedState, int userId) { + @Override + public void packageStateChanged(String packageName, int changedState, int userId) { // We don't early out here in different cases where we could potentially early-out (e.g. if // we receive PACKAGE_CHANGED for another user than the system user) since that would // complicate this logic further and open up for more edge cases. @@ -163,7 +164,8 @@ class WebViewUpdateServiceImpl { } } - void prepareWebViewInSystemServer() { + @Override + public void prepareWebViewInSystemServer() { mSystemInterface.notifyZygote(isMultiProcessEnabled()); try { synchronized (mLock) { @@ -210,7 +212,8 @@ class WebViewUpdateServiceImpl { mSystemInterface.ensureZygoteStarted(); } - void handleNewUser(int userId) { + @Override + public void handleNewUser(int userId) { // The system user is always started at boot, and by that point we have already run one // round of the package-changing logic (through prepareWebViewInSystemServer()), so early // out here. @@ -218,7 +221,8 @@ class WebViewUpdateServiceImpl { handleUserChange(); } - void handleUserRemoved(int userId) { + @Override + public void handleUserRemoved(int userId) { handleUserChange(); } @@ -232,14 +236,16 @@ class WebViewUpdateServiceImpl { updateCurrentWebViewPackage(null); } - void notifyRelroCreationCompleted() { + @Override + public void notifyRelroCreationCompleted() { synchronized (mLock) { mNumRelroCreationsFinished++; checkIfRelrosDoneLocked(); } } - WebViewProviderResponse waitForAndGetProvider() { + @Override + public WebViewProviderResponse waitForAndGetProvider() { PackageInfo webViewPackage = null; final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS; boolean webViewReady = false; @@ -284,7 +290,8 @@ class WebViewUpdateServiceImpl { * replacing that provider it will not be in use directly, but will be used when the relros * or the replacement are done). */ - String changeProviderAndSetting(String newProviderName) { + @Override + public String changeProviderAndSetting(String newProviderName) { PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName); if (newPackage == null) return ""; return newPackage.packageName; @@ -367,7 +374,8 @@ class WebViewUpdateServiceImpl { /** * Fetch only the currently valid WebView packages. **/ - WebViewProviderInfo[] getValidWebViewPackages() { + @Override + public WebViewProviderInfo[] getValidWebViewPackages() { ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos(); WebViewProviderInfo[] providers = new WebViewProviderInfo[providersAndPackageInfos.length]; @@ -464,11 +472,13 @@ class WebViewUpdateServiceImpl { return true; } - WebViewProviderInfo[] getWebViewPackages() { + @Override + public WebViewProviderInfo[] getWebViewPackages() { return mSystemInterface.getWebViewPackages(); } - PackageInfo getCurrentWebViewPackage() { + @Override + public PackageInfo getCurrentWebViewPackage() { synchronized (mLock) { return mCurrentWebViewPackage; } @@ -620,7 +630,8 @@ class WebViewUpdateServiceImpl { return null; } - boolean isMultiProcessEnabled() { + @Override + public boolean isMultiProcessEnabled() { int settingValue = mSystemInterface.getMultiProcessSetting(mContext); if (mSystemInterface.isMultiProcessDefaultEnabled()) { // Multiprocess should be enabled unless the user has turned it off manually. @@ -631,7 +642,8 @@ class WebViewUpdateServiceImpl { } } - void enableMultiProcess(boolean enable) { + @Override + public void enableMultiProcess(boolean enable) { PackageInfo current = getCurrentWebViewPackage(); mSystemInterface.setMultiProcessSetting(mContext, enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE); @@ -644,7 +656,8 @@ class WebViewUpdateServiceImpl { /** * Dump the state of this Service. */ - void dumpState(PrintWriter pw) { + @Override + public void dumpState(PrintWriter pw) { pw.println("Current WebView Update Service state"); pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled())); synchronized (mLock) { diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java new file mode 100644 index 000000000000..e618c7e2a80c --- /dev/null +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java @@ -0,0 +1,747 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.webkit; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.os.AsyncTask; +import android.os.Trace; +import android.os.UserHandle; +import android.util.Slog; +import android.webkit.UserPackage; +import android.webkit.WebViewFactory; +import android.webkit.WebViewProviderInfo; +import android.webkit.WebViewProviderResponse; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of the WebViewUpdateService. + * This class doesn't depend on the android system like the actual Service does and can be used + * directly by tests (as long as they implement a SystemInterface). + * + * This class keeps track of and prepares the current WebView implementation, and needs to keep + * track of a couple of different things such as what package is used as WebView implementation. + * + * The package-visible methods in this class are accessed from WebViewUpdateService either on the UI + * thread or on one of multiple Binder threads. The WebView preparation code shares state between + * threads meaning that code that chooses a new WebView implementation or checks which + * implementation is being used needs to hold a lock. + * + * The WebViewUpdateService can be accessed in a couple of different ways. + * 1. It is started from the SystemServer at boot - at that point we just initiate some state such + * as the WebView preparation class. + * 2. The SystemServer calls WebViewUpdateService.prepareWebViewInSystemServer. This happens at boot + * and the WebViewUpdateService should not have been accessed before this call. In this call we + * choose WebView implementation for the first time. + * 3. The update service listens for Intents related to package installs and removals. These intents + * are received and processed on the UI thread. Each intent can result in changing WebView + * implementation. + * 4. The update service can be reached through Binder calls which are handled on specific binder + * threads. These calls can be made from any process. Generally they are used for changing WebView + * implementation (from Settings), getting information about the current WebView implementation (for + * loading WebView into an app process), or notifying the service about Relro creation being + * completed. + * + * @hide + */ +class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { + private static final String TAG = WebViewUpdateServiceImpl2.class.getSimpleName(); + + private static class WebViewPackageMissingException extends Exception { + WebViewPackageMissingException(String message) { + super(message); + } + + WebViewPackageMissingException(Exception e) { + super(e); + } + } + + private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000. + private static final long NS_PER_MS = 1000000; + + private static final int VALIDITY_OK = 0; + private static final int VALIDITY_INCORRECT_SDK_VERSION = 1; + private static final int VALIDITY_INCORRECT_VERSION_CODE = 2; + private static final int VALIDITY_INCORRECT_SIGNATURE = 3; + private static final int VALIDITY_NO_LIBRARY_FLAG = 4; + + private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE; + private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE; + + private final SystemInterface mSystemInterface; + private final Context mContext; + + private long mMinimumVersionCode = -1; + + // Keeps track of the number of running relro creations + private int mNumRelroCreationsStarted = 0; + private int mNumRelroCreationsFinished = 0; + // Implies that we need to rerun relro creation because we are using an out-of-date package + private boolean mWebViewPackageDirty = false; + private boolean mAnyWebViewInstalled = false; + + private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE; + + // The WebView package currently in use (or the one we are preparing). + private PackageInfo mCurrentWebViewPackage = null; + + private final Object mLock = new Object(); + + WebViewUpdateServiceImpl2(Context context, SystemInterface systemInterface) { + mContext = context; + mSystemInterface = systemInterface; + } + + @Override + public void packageStateChanged(String packageName, int changedState, int userId) { + // We don't early out here in different cases where we could potentially early-out (e.g. if + // we receive PACKAGE_CHANGED for another user than the system user) since that would + // complicate this logic further and open up for more edge cases. + for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) { + String webviewPackage = provider.packageName; + + if (webviewPackage.equals(packageName)) { + boolean updateWebView = false; + boolean removedOrChangedOldPackage = false; + String oldProviderName = null; + PackageInfo newPackage = null; + synchronized (mLock) { + try { + newPackage = findPreferredWebViewPackage(); + if (mCurrentWebViewPackage != null) { + oldProviderName = mCurrentWebViewPackage.packageName; + } + // Only trigger update actions if the updated package is the one + // that will be used, or the one that was in use before the + // update, or if we haven't seen a valid WebView package before. + updateWebView = + provider.packageName.equals(newPackage.packageName) + || provider.packageName.equals(oldProviderName) + || mCurrentWebViewPackage == null; + // We removed the old package if we received an intent to remove + // or replace the old package. + removedOrChangedOldPackage = + provider.packageName.equals(oldProviderName); + if (updateWebView) { + onWebViewProviderChanged(newPackage); + } + } catch (WebViewPackageMissingException e) { + mCurrentWebViewPackage = null; + Slog.e(TAG, "Could not find valid WebView package to create relro with " + + e); + } + } + if (updateWebView && !removedOrChangedOldPackage + && oldProviderName != null) { + // If the provider change is the result of adding or replacing a + // package that was not the previous provider then we must kill + // packages dependent on the old package ourselves. The framework + // only kills dependents of packages that are being removed. + mSystemInterface.killPackageDependents(oldProviderName); + } + return; + } + } + } + + @Override + public void prepareWebViewInSystemServer() { + mSystemInterface.notifyZygote(isMultiProcessEnabled()); + try { + synchronized (mLock) { + mCurrentWebViewPackage = findPreferredWebViewPackage(); + String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext); + if (userSetting != null + && !userSetting.equals(mCurrentWebViewPackage.packageName)) { + // Don't persist the user-chosen setting across boots if the package being + // chosen is not used (could be disabled or uninstalled) so that the user won't + // be surprised by the device switching to using a certain webview package, + // that was uninstalled/disabled a long time ago, if it is installed/enabled + // again. + mSystemInterface.updateUserSetting(mContext, + mCurrentWebViewPackage.packageName); + } + onWebViewProviderChanged(mCurrentWebViewPackage); + } + } catch (Throwable t) { + // Log and discard errors at this stage as we must not crash the system server. + Slog.e(TAG, "error preparing webview provider from system server", t); + } + + if (getCurrentWebViewPackage() == null) { + // We didn't find a valid WebView implementation. Try explicitly re-enabling the + // fallback package for all users in case it was disabled, even if we already did the + // one-time migration before. If this actually changes the state, we will see the + // PackageManager broadcast shortly and try again. + WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); + WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders); + if (fallbackProvider != null) { + Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName); + mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName, + true); + } else { + Slog.e(TAG, "No valid provider and no fallback available."); + } + } + } + + private void startZygoteWhenReady() { + // Wait on a background thread for RELRO creation to be done. We ignore the return value + // because even if RELRO creation failed we still want to start the zygote. + waitForAndGetProvider(); + mSystemInterface.ensureZygoteStarted(); + } + + @Override + public void handleNewUser(int userId) { + // The system user is always started at boot, and by that point we have already run one + // round of the package-changing logic (through prepareWebViewInSystemServer()), so early + // out here. + if (userId == UserHandle.USER_SYSTEM) return; + handleUserChange(); + } + + @Override + public void handleUserRemoved(int userId) { + handleUserChange(); + } + + /** + * Called when a user was added or removed to ensure WebView preparation is triggered. + * This has to be done since the WebView package we use depends on the enabled-state + * of packages for all users (so adding or removing a user might cause us to change package). + */ + private void handleUserChange() { + // Potentially trigger package-changing logic. + updateCurrentWebViewPackage(null); + } + + @Override + public void notifyRelroCreationCompleted() { + synchronized (mLock) { + mNumRelroCreationsFinished++; + checkIfRelrosDoneLocked(); + } + } + + @Override + public WebViewProviderResponse waitForAndGetProvider() { + PackageInfo webViewPackage = null; + final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS; + boolean webViewReady = false; + int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS; + synchronized (mLock) { + webViewReady = webViewIsReadyLocked(); + while (!webViewReady) { + final long timeNowMs = System.nanoTime() / NS_PER_MS; + if (timeNowMs >= timeoutTimeMs) break; + try { + mLock.wait(timeoutTimeMs - timeNowMs); + } catch (InterruptedException e) { + // ignore + } + webViewReady = webViewIsReadyLocked(); + } + // Make sure we return the provider that was used to create the relro file + webViewPackage = mCurrentWebViewPackage; + if (webViewReady) { + // success + } else if (!mAnyWebViewInstalled) { + webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES; + } else { + // Either the current relro creation isn't done yet, or the new relro creatioin + // hasn't kicked off yet (the last relro creation used an out-of-date WebView). + webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO; + String timeoutError = "Timed out waiting for relro creation, relros started " + + mNumRelroCreationsStarted + + " relros finished " + mNumRelroCreationsFinished + + " package dirty? " + mWebViewPackageDirty; + Slog.e(TAG, timeoutError); + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, timeoutError); + } + } + if (!webViewReady) Slog.w(TAG, "creating relro file timed out"); + return new WebViewProviderResponse(webViewPackage, webViewStatus); + } + + /** + * Change WebView provider and provider setting and kill packages using the old provider. + * Return the new provider (in case we are in the middle of creating relro files, or + * replacing that provider it will not be in use directly, but will be used when the relros + * or the replacement are done). + */ + @Override + public String changeProviderAndSetting(String newProviderName) { + PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName); + if (newPackage == null) return ""; + return newPackage.packageName; + } + + /** + * Update the current WebView package. + * @param newProviderName the package to switch to, null if no package has been explicitly + * chosen. + */ + private PackageInfo updateCurrentWebViewPackage(@Nullable String newProviderName) { + PackageInfo oldPackage = null; + PackageInfo newPackage = null; + boolean providerChanged = false; + synchronized (mLock) { + oldPackage = mCurrentWebViewPackage; + + if (newProviderName != null) { + mSystemInterface.updateUserSetting(mContext, newProviderName); + } + + try { + newPackage = findPreferredWebViewPackage(); + providerChanged = (oldPackage == null) + || !newPackage.packageName.equals(oldPackage.packageName); + } catch (WebViewPackageMissingException e) { + // If updated the Setting but don't have an installed WebView package, the + // Setting will be used when a package is available. + mCurrentWebViewPackage = null; + Slog.e(TAG, "Couldn't find WebView package to use " + e); + return null; + } + // Perform the provider change if we chose a new provider + if (providerChanged) { + onWebViewProviderChanged(newPackage); + } + } + // Kill apps using the old provider only if we changed provider + if (providerChanged && oldPackage != null) { + mSystemInterface.killPackageDependents(oldPackage.packageName); + } + // Return the new provider, this is not necessarily the one we were asked to switch to, + // but the persistent setting will now be pointing to the provider we were asked to + // switch to anyway. + return newPackage; + } + + /** + * This is called when we change WebView provider, either when the current provider is + * updated or a new provider is chosen / takes precedence. + */ + private void onWebViewProviderChanged(PackageInfo newPackage) { + synchronized (mLock) { + mAnyWebViewInstalled = true; + if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) { + mCurrentWebViewPackage = newPackage; + + // The relro creations might 'finish' (not start at all) before + // WebViewFactory.onWebViewProviderChanged which means we might not know the + // number of started creations before they finish. + mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN; + mNumRelroCreationsFinished = 0; + mNumRelroCreationsStarted = + mSystemInterface.onWebViewProviderChanged(newPackage); + // If the relro creations finish before we know the number of started creations + // we will have to do any cleanup/notifying here. + checkIfRelrosDoneLocked(); + } else { + mWebViewPackageDirty = true; + } + } + + // Once we've notified the system that the provider has changed and started RELRO creation, + // try to restart the zygote so that it will be ready when apps use it. + if (isMultiProcessEnabled()) { + AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady); + } + } + + /** + * Fetch only the currently valid WebView packages. + **/ + @Override + public WebViewProviderInfo[] getValidWebViewPackages() { + ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos(); + WebViewProviderInfo[] providers = + new WebViewProviderInfo[providersAndPackageInfos.length]; + for (int n = 0; n < providersAndPackageInfos.length; n++) { + providers[n] = providersAndPackageInfos[n].provider; + } + return providers; + } + + private static class ProviderAndPackageInfo { + public final WebViewProviderInfo provider; + public final PackageInfo packageInfo; + + ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) { + this.provider = provider; + this.packageInfo = packageInfo; + } + } + + private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() { + WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages(); + List<ProviderAndPackageInfo> providers = new ArrayList<>(); + for (int n = 0; n < allProviders.length; n++) { + try { + PackageInfo packageInfo = + mSystemInterface.getPackageInfoForProvider(allProviders[n]); + if (validityResult(allProviders[n], packageInfo) == VALIDITY_OK) { + providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo)); + } + } catch (NameNotFoundException e) { + // Don't add non-existent packages + } + } + return providers.toArray(new ProviderAndPackageInfo[providers.size()]); + } + + /** + * Returns either the package info of the WebView provider determined in the following way: + * If the user has chosen a provider then use that if it is valid, + * otherwise use the first package in the webview priority list that is valid. + * + */ + private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException { + ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos(); + + String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext); + + // If the user has chosen provider, use that (if it's installed and enabled for all + // users). + for (ProviderAndPackageInfo providerAndPackage : providers) { + if (providerAndPackage.provider.packageName.equals(userChosenProvider)) { + // userPackages can contain null objects. + List<UserPackage> userPackages = + mSystemInterface.getPackageInfoForProviderAllUsers(mContext, + providerAndPackage.provider); + if (isInstalledAndEnabledForAllUsers(userPackages)) { + return providerAndPackage.packageInfo; + } + } + } + + // User did not choose, or the choice failed; use the most stable provider that is + // installed and enabled for all users, and available by default (not through + // user choice). + for (ProviderAndPackageInfo providerAndPackage : providers) { + if (providerAndPackage.provider.availableByDefault) { + // userPackages can contain null objects. + List<UserPackage> userPackages = + mSystemInterface.getPackageInfoForProviderAllUsers(mContext, + providerAndPackage.provider); + if (isInstalledAndEnabledForAllUsers(userPackages)) { + return providerAndPackage.packageInfo; + } + } + } + + // This should never happen during normal operation (only with modified system images). + mAnyWebViewInstalled = false; + throw new WebViewPackageMissingException("Could not find a loadable WebView package"); + } + + /** + * Return true iff {@param packageInfos} point to only installed and enabled packages. + * The given packages {@param packageInfos} should all be pointing to the same package, but each + * PackageInfo representing a different user's package. + */ + private static boolean isInstalledAndEnabledForAllUsers( + List<UserPackage> userPackages) { + for (UserPackage userPackage : userPackages) { + if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) { + return false; + } + } + return true; + } + + @Override + public WebViewProviderInfo[] getWebViewPackages() { + return mSystemInterface.getWebViewPackages(); + } + + @Override + public PackageInfo getCurrentWebViewPackage() { + synchronized (mLock) { + return mCurrentWebViewPackage; + } + } + + /** + * Returns whether WebView is ready and is not going to go through its preparation phase + * again directly. + */ + private boolean webViewIsReadyLocked() { + return !mWebViewPackageDirty + && (mNumRelroCreationsStarted == mNumRelroCreationsFinished) + // The current package might be replaced though we haven't received an intent + // declaring this yet, the following flag makes anyone loading WebView to wait in + // this case. + && mAnyWebViewInstalled; + } + + private void checkIfRelrosDoneLocked() { + if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) { + if (mWebViewPackageDirty) { + mWebViewPackageDirty = false; + // If we have changed provider since we started the relro creation we need to + // redo the whole process using the new package instead. + try { + PackageInfo newPackage = findPreferredWebViewPackage(); + onWebViewProviderChanged(newPackage); + } catch (WebViewPackageMissingException e) { + mCurrentWebViewPackage = null; + // If we can't find any valid WebView package we are now in a state where + // mAnyWebViewInstalled is false, so loading WebView will be blocked and we + // should simply wait until we receive an intent declaring a new package was + // installed. + } + } else { + mLock.notifyAll(); + } + } + } + + private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) { + // Ensure the provider targets this framework release (or a later one). + if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) { + return VALIDITY_INCORRECT_SDK_VERSION; + } + if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode()) + && !mSystemInterface.systemIsDebuggable()) { + // Webview providers may be downgraded arbitrarily low, prevent that by enforcing + // minimum version code. This check is only enforced for user builds. + return VALIDITY_INCORRECT_VERSION_CODE; + } + if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) { + return VALIDITY_INCORRECT_SIGNATURE; + } + if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) { + return VALIDITY_NO_LIBRARY_FLAG; + } + return VALIDITY_OK; + } + + /** + * Both versionCodes should be from a WebView provider package implemented by Chromium. + * VersionCodes from other kinds of packages won't make any sense in this method. + * + * An introduction to Chromium versionCode scheme: + * "BBBBPPPXX" + * BBBB: 4 digit branch number. It monotonically increases over time. + * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits + * may change their meaning in the future. + * XX: Digits to differentiate different APK builds of the same source version. + * + * This method takes the "BBBB" of versionCodes and compare them. + * + * https://www.chromium.org/developers/version-numbers describes general Chromium versioning; + * https://source.chromium.org/chromium/chromium/src/+/master:build/util/android_chrome_version.py + * is the canonical source for how Chromium versionCodes are calculated. + * + * @return true if versionCode1 is higher than or equal to versionCode2. + */ + private static boolean versionCodeGE(long versionCode1, long versionCode2) { + long v1 = versionCode1 / 100000; + long v2 = versionCode2 / 100000; + + return v1 >= v2; + } + + /** + * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode + * of all available-by-default WebView provider packages. If there is no such WebView provider + * package on the system, then return -1, which means all positive versionCode WebView packages + * are accepted. + * + * Note that this is a private method that handles a variable (mMinimumVersionCode) which is + * shared between threads. Furthermore, this method does not hold mLock meaning that we must + * take extra care to ensure this method is thread-safe. + */ + private long getMinimumVersionCode() { + if (mMinimumVersionCode > 0) { + return mMinimumVersionCode; + } + + long minimumVersionCode = -1; + for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) { + if (provider.availableByDefault) { + try { + long versionCode = + mSystemInterface.getFactoryPackageVersion(provider.packageName); + if (minimumVersionCode < 0 || versionCode < minimumVersionCode) { + minimumVersionCode = versionCode; + } + } catch (NameNotFoundException e) { + // Safe to ignore. + } + } + } + + mMinimumVersionCode = minimumVersionCode; + return mMinimumVersionCode; + } + + private static boolean providerHasValidSignature(WebViewProviderInfo provider, + PackageInfo packageInfo, SystemInterface systemInterface) { + // Skip checking signatures on debuggable builds, for development purposes. + if (systemInterface.systemIsDebuggable()) return true; + + // Allow system apps to be valid providers regardless of signature. + if (packageInfo.applicationInfo.isSystemApp()) return true; + + // We don't support packages with multiple signatures. + if (packageInfo.signatures.length != 1) return false; + + // If any of the declared signatures match the package signature, it's valid. + for (Signature signature : provider.signatures) { + if (signature.equals(packageInfo.signatures[0])) return true; + } + + return false; + } + + /** + * Returns the only fallback provider in the set of given packages, or null if there is none. + */ + private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) { + for (WebViewProviderInfo provider : webviewPackages) { + if (provider.isFallback) { + return provider; + } + } + return null; + } + + @Override + public boolean isMultiProcessEnabled() { + int settingValue = mSystemInterface.getMultiProcessSetting(mContext); + if (mSystemInterface.isMultiProcessDefaultEnabled()) { + // Multiprocess should be enabled unless the user has turned it off manually. + return settingValue > MULTIPROCESS_SETTING_OFF_VALUE; + } else { + // Multiprocess should not be enabled, unless the user has turned it on manually. + return settingValue >= MULTIPROCESS_SETTING_ON_VALUE; + } + } + + @Override + public void enableMultiProcess(boolean enable) { + PackageInfo current = getCurrentWebViewPackage(); + mSystemInterface.setMultiProcessSetting(mContext, + enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE); + mSystemInterface.notifyZygote(enable); + if (current != null) { + mSystemInterface.killPackageDependents(current.packageName); + } + } + + /** + * Dump the state of this Service. + */ + @Override + public void dumpState(PrintWriter pw) { + pw.println("Current WebView Update Service state"); + pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled())); + synchronized (mLock) { + if (mCurrentWebViewPackage == null) { + pw.println(" Current WebView package is null"); + } else { + pw.println(String.format(" Current WebView package (name, version): (%s, %s)", + mCurrentWebViewPackage.packageName, + mCurrentWebViewPackage.versionName)); + } + pw.println(String.format(" Minimum targetSdkVersion: %d", + UserPackage.MINIMUM_SUPPORTED_SDK)); + pw.println(String.format(" Minimum WebView version code: %d", + mMinimumVersionCode)); + pw.println(String.format(" Number of relros started: %d", + mNumRelroCreationsStarted)); + pw.println(String.format(" Number of relros finished: %d", + mNumRelroCreationsFinished)); + pw.println(String.format(" WebView package dirty: %b", mWebViewPackageDirty)); + pw.println(String.format(" Any WebView package installed: %b", + mAnyWebViewInstalled)); + + try { + PackageInfo preferredWebViewPackage = findPreferredWebViewPackage(); + pw.println(String.format( + " Preferred WebView package (name, version): (%s, %s)", + preferredWebViewPackage.packageName, + preferredWebViewPackage.versionName)); + } catch (WebViewPackageMissingException e) { + pw.println(String.format(" Preferred WebView package: none")); + } + + dumpAllPackageInformationLocked(pw); + } + } + + private void dumpAllPackageInformationLocked(PrintWriter pw) { + WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages(); + pw.println(" WebView packages:"); + for (WebViewProviderInfo provider : allProviders) { + List<UserPackage> userPackages = + mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider); + PackageInfo systemUserPackageInfo = + userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo(); + if (systemUserPackageInfo == null) { + pw.println(String.format(" %s is NOT installed.", provider.packageName)); + continue; + } + + int validity = validityResult(provider, systemUserPackageInfo); + String packageDetails = String.format( + "versionName: %s, versionCode: %d, targetSdkVersion: %d", + systemUserPackageInfo.versionName, + systemUserPackageInfo.getLongVersionCode(), + systemUserPackageInfo.applicationInfo.targetSdkVersion); + if (validity == VALIDITY_OK) { + boolean installedForAllUsers = isInstalledAndEnabledForAllUsers( + mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider)); + pw.println(String.format( + " Valid package %s (%s) is %s installed/enabled for all users", + systemUserPackageInfo.packageName, + packageDetails, + installedForAllUsers ? "" : "NOT")); + } else { + pw.println(String.format(" Invalid package %s (%s), reason: %s", + systemUserPackageInfo.packageName, + packageDetails, + getInvalidityReason(validity))); + } + } + } + + private static String getInvalidityReason(int invalidityReason) { + switch (invalidityReason) { + case VALIDITY_INCORRECT_SDK_VERSION: + return "SDK version too low"; + case VALIDITY_INCORRECT_VERSION_CODE: + return "Version code too low"; + case VALIDITY_INCORRECT_SIGNATURE: + return "Incorrect signature"; + case VALIDITY_NO_LIBRARY_FLAG: + return "No WebView-library manifest flag"; + default: + return "Unexcepted validity-reason"; + } + } +} diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java new file mode 100644 index 000000000000..a9c3dc45842f --- /dev/null +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.webkit; + +import android.content.pm.PackageInfo; +import android.webkit.WebViewProviderInfo; +import android.webkit.WebViewProviderResponse; + +import java.io.PrintWriter; + +interface WebViewUpdateServiceInterface { + void packageStateChanged(String packageName, int changedState, int userId); + + void handleNewUser(int userId); + + void handleUserRemoved(int userId); + + WebViewProviderInfo[] getWebViewPackages(); + + void prepareWebViewInSystemServer(); + + void notifyRelroCreationCompleted(); + + WebViewProviderResponse waitForAndGetProvider(); + + String changeProviderAndSetting(String newProviderName); + + WebViewProviderInfo[] getValidWebViewPackages(); + + PackageInfo getCurrentWebViewPackage(); + + boolean isMultiProcessEnabled(); + + void enableMultiProcess(boolean enable); + + void dumpState(PrintWriter pw); +} diff --git a/services/core/java/com/android/server/webkit/flags.aconfig b/services/core/java/com/android/server/webkit/flags.aconfig new file mode 100644 index 000000000000..1411acc4ab84 --- /dev/null +++ b/services/core/java/com/android/server/webkit/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.webkit" + +flag { + name: "update_service_v2" + namespace: "webview" + description: "Using a new version of the WebView update service" + bug: "308907090" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index faccca8981c8..315e7d85df24 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -55,6 +55,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH; import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller; +import static com.android.window.flags.Flags.allowDisableActivityRecordInputSink; import android.Manifest; import android.annotation.ColorInt; @@ -1688,4 +1689,20 @@ class ActivityClientController extends IActivityClientController.Stub { return r.mRequestedLaunchingTaskFragmentToken == taskFragmentToken; } } + + @Override + public void setActivityRecordInputSinkEnabled(IBinder activityToken, boolean enabled) { + if (!allowDisableActivityRecordInputSink()) { + return; + } + + mService.mAmInternal.enforceCallingPermission( + Manifest.permission.INTERNAL_SYSTEM_WINDOW, "setActivityRecordInputSinkEnabled"); + synchronized (mGlobalLock) { + final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken); + if (r != null) { + r.mActivityRecordInputSinkEnabled = enabled; + } + } + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 081759d563a5..d90d4ff6bbd6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -970,6 +970,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean mWaitForEnteringPinnedMode; final ActivityRecordInputSink mActivityRecordInputSink; + // System activities with INTERNAL_SYSTEM_WINDOW can disable ActivityRecordInputSink. + boolean mActivityRecordInputSinkEnabled = true; // Activities with this uid are allowed to not create an input sink while being in the same // task and directly above this ActivityRecord. This field is updated whenever a new activity diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index be7d9b63f779..c61d86355c8b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -86,7 +86,8 @@ class ActivityRecordInputSink { final boolean allowPassthrough = activityBelowInTask != null && ( activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid() || activityBelowInTask.isUid(mActivityRecord.getUid())); - if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()) { + if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition() + || !mActivityRecord.mActivityRecordInputSinkEnabled) { mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE, InputConfig.NOT_TOUCHABLE); } else { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 009b8e048840..5e0a449ddd63 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -133,6 +133,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; import com.android.server.wm.BackgroundActivityStartController.BalCode; +import com.android.server.wm.BackgroundActivityStartController.BalVerdict; import com.android.server.wm.LaunchParamsController.LaunchParams; import com.android.server.wm.TaskFragment.EmbeddingCheckResult; @@ -1090,14 +1091,14 @@ class ActivityStarter { ActivityOptions checkedOptions = options != null ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; - @BalCode int balCode = BAL_ALLOW_DEFAULT; + final BalVerdict balVerdict; if (!abort) { try { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "shouldAbortBackgroundActivityStart"); BackgroundActivityStartController balController = mSupervisor.getBackgroundActivityLaunchController(); - BackgroundActivityStartController.BalVerdict balVerdict = + balVerdict = balController.checkBackgroundActivityStart( callingUid, callingPid, @@ -1109,13 +1110,13 @@ class ActivityStarter { request.forcedBalByPiSender, intent, checkedOptions); - balCode = balVerdict.getCode(); - request.logMessage.append(" (").append( - BackgroundActivityStartController.balCodeToString(balCode)) - .append(")"); + request.logMessage.append(" (").append(balVerdict).append(")"); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } + } else { + // Sets ALLOW_BY_DEFAULT as default value as the activity launch will be aborted anyway. + balVerdict = BalVerdict.ALLOW_BY_DEFAULT; } if (request.allowPendingRemoteAnimationRegistryLookup) { @@ -1293,13 +1294,13 @@ class ActivityStarter { WindowProcessController homeProcess = mService.mHomeProcess; boolean isHomeProcess = homeProcess != null && aInfo.applicationInfo.uid == homeProcess.mUid; - if (balCode != BAL_BLOCK && !isHomeProcess) { + if (balVerdict.allows() && !isHomeProcess) { mService.resumeAppSwitches(); } mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession, request.voiceInteractor, startFlags, checkedOptions, - inTask, inTaskFragment, balCode, intentGrants, realCallingUid); + inTask, inTaskFragment, balVerdict, intentGrants, realCallingUid); if (request.outActivity != null) { request.outActivity[0] = mLastStartActivityRecord; @@ -1449,7 +1450,8 @@ class ActivityStarter { private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, ActivityOptions options, Task inTask, - TaskFragment inTaskFragment, @BalCode int balCode, + TaskFragment inTaskFragment, + BalVerdict balVerdict, NeededUriGrants intentGrants, int realCallingUid) { int result = START_CANCELED; final Task startedActivityRootTask; @@ -1468,7 +1470,7 @@ class ActivityStarter { try { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner"); result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor, - startFlags, options, inTask, inTaskFragment, balCode, + startFlags, options, inTask, inTaskFragment, balVerdict, intentGrants, realCallingUid); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); @@ -1615,10 +1617,10 @@ class ActivityStarter { int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, ActivityOptions options, Task inTask, - TaskFragment inTaskFragment, @BalCode int balCode, + TaskFragment inTaskFragment, BalVerdict balVerdict, NeededUriGrants intentGrants, int realCallingUid) { setInitialState(r, options, inTask, inTaskFragment, startFlags, sourceRecord, - voiceSession, voiceInteractor, balCode, realCallingUid); + voiceSession, voiceInteractor, balVerdict.getCode(), realCallingUid); computeLaunchingTaskFlags(); mIntent.setFlags(mLaunchFlags); @@ -1696,7 +1698,8 @@ class ActivityStarter { } recordTransientLaunchIfNeeded(targetTaskTop); // Recycle the target task for this launch. - startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants); + startResult = + recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants, balVerdict); if (startResult != START_SUCCESS) { return startResult; } @@ -1730,6 +1733,7 @@ class ActivityStarter { recordTransientLaunchIfNeeded(mLastStartActivityRecord); if (!mAvoidMoveToFront && mDoResume) { + logOnlyCreatorAllowsBAL(balVerdict, realCallingUid, newTask); mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask); if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.isDreaming() && !dreamStopping) { @@ -1800,6 +1804,7 @@ class ActivityStarter { // now update the focused root-task accordingly. if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable() && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) { + logOnlyCreatorAllowsBAL(balVerdict, realCallingUid, newTask); mTargetRootTask.moveToFront("startActivityInner"); } mRootWindowContainer.resumeFocusedTasksTopActivities( @@ -1817,7 +1822,7 @@ class ActivityStarter { // Note that mStartActivity and source should be in the same Task at this point. if (mOptions != null && mOptions.isLaunchIntoPip() && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask() - && balCode != BAL_BLOCK) { + && balVerdict.allows()) { mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity, sourceRecord, "launch-into-pip"); } @@ -1828,6 +1833,24 @@ class ActivityStarter { return START_SUCCESS; } + private void logOnlyCreatorAllowsBAL(BalVerdict balVerdict, + int realCallingUid, boolean newTask) { + // TODO (b/296478675) eventually, we will prevent such case from happening + // and probably also log that a BAL is prevented by android V. + if (!newTask && balVerdict.onlyCreatorAllows()) { + String realCallingPackage = + mService.mContext.getPackageManager().getNameForUid(realCallingUid); + if (realCallingPackage == null) { + realCallingPackage = "uid=" + realCallingUid; + } + Slog.wtf(TAG, "A background app is brought to the foreground due to a " + + "PendingIntent. However, only the creator of the PendingIntent allows BAL, " + + "while the sender does not allow BAL. realCallingPackage: " + + realCallingPackage + "; callingPackage: " + mRequest.callingPackage + + "; mTargetRootTask:" + mTargetRootTask); + } + } + private void recordTransientLaunchIfNeeded(ActivityRecord r) { if (r == null || !mTransientLaunch) return; final TransitionController controller = r.mTransitionController; @@ -1995,7 +2018,7 @@ class ActivityStarter { */ @VisibleForTesting int recycleTask(Task targetTask, ActivityRecord targetTaskTop, Task reusedTask, - NeededUriGrants intentGrants) { + NeededUriGrants intentGrants, BalVerdict balVerdict) { // Should not recycle task which is from a different user, just adding the starting // activity to the task. if (targetTask.mUserId != mStartActivity.mUserId) { @@ -2024,7 +2047,7 @@ class ActivityStarter { mRootWindowContainer.startPowerModeLaunchIfNeeded(false /* forceSend */, targetTaskTop); - setTargetRootTaskIfNeeded(targetTaskTop); + setTargetRootTaskIfNeeded(targetTaskTop, balVerdict); // When there is a reused activity and the current result is a trampoline activity, // set the reused activity as the result. @@ -2040,6 +2063,7 @@ class ActivityStarter { if (!mMovedToFront && mDoResume) { ProtoLog.d(WM_DEBUG_TASKS, "Bring to front target: %s from %s", mTargetRootTask, targetTaskTop); + logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false); mTargetRootTask.moveToFront("intentActivityFound"); } resumeTargetRootTaskIfNeeded(); @@ -2068,6 +2092,7 @@ class ActivityStarter { targetTaskTop.showStartingWindow(true /* taskSwitch */); } else if (mDoResume) { // Make sure the root task and its belonging display are moved to topmost. + logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false); mTargetRootTask.moveToFront("intentActivityFound"); } // We didn't do anything... but it was needed (a.k.a., client don't use that intent!) @@ -2663,7 +2688,7 @@ class ActivityStarter { * @param intentActivity Existing matching activity. * @return {@link ActivityRecord} brought to front. */ - private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity) { + private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity, BalVerdict balVerdict) { intentActivity.getTaskFragment().clearLastPausedActivity(); Task intentTask = intentActivity.getTask(); // The intent task might be reparented while in getOrCreateRootTask, caches the original @@ -2730,6 +2755,7 @@ class ActivityStarter { // task on top there. // Defer resuming the top activity while moving task to top, since the // current task-top activity may not be the activity that should be resumed. + logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false); mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, DEFER_RESUME, "bringingFoundTaskToFront"); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index a9f11e488881..34c7eee45f34 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -281,6 +281,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wallpaper.WallpaperManagerInternal; +import com.android.wm.shell.Flags; import java.io.BufferedReader; import java.io.File; @@ -316,8 +317,6 @@ import java.util.Set; public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender"; private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM; - private static final String ENABLE_PIP2_IMPLEMENTATION = - "persist.wm.debug.enable_pip2_implementation"; static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK; static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; @@ -7262,6 +7261,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } static boolean isPip2ExperimentEnabled() { - return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false); + return Flags.enablePip2Implementation(); } } diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index b66c4c326a58..8cc197c2f3d0 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -46,7 +46,6 @@ import android.app.BackgroundStartPrivileges; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; -import android.compat.annotation.Overridable; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; @@ -88,7 +87,6 @@ public class BackgroundActivityStartController { /** If enabled the creator will not allow BAL on its behalf by default. */ @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - @Overridable private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR = 296478951; public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED = @@ -433,10 +431,15 @@ public class BackgroundActivityStartController { static class BalVerdict { static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked"); + static final BalVerdict ALLOW_BY_DEFAULT = + new BalVerdict(BAL_ALLOW_DEFAULT, false, "Default"); private final @BalCode int mCode; private final boolean mBackground; private final String mMessage; private String mProcessInfo; + // indicates BAL would be blocked because only creator of the PI has the privilege to allow + // BAL, the sender does not have the privilege to allow BAL. + private boolean mOnlyCreatorAllows; BalVerdict(@BalCode int balCode, boolean background, String message) { this.mBackground = background; @@ -457,6 +460,15 @@ public class BackgroundActivityStartController { return !blocks(); } + BalVerdict setOnlyCreatorAllows(boolean onlyCreatorAllows) { + mOnlyCreatorAllows = onlyCreatorAllows; + return this; + } + + boolean onlyCreatorAllows() { + return mOnlyCreatorAllows; + } + public String toString() { StringBuilder builder = new StringBuilder(); builder.append(balCodeToString(mCode)); @@ -566,6 +578,10 @@ public class BackgroundActivityStartController { BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows() ? resultForCaller : checkBackgroundActivityStartAllowedBySender(state, checkedOptions); + if (state.isPendingIntent()) { + resultForCaller.setOnlyCreatorAllows( + resultForCaller.allows() && resultForRealCaller.blocks()); + } if (resultForCaller.allows() && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java index 28f656e624fb..8b282dd3e7a8 100644 --- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java +++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java @@ -58,6 +58,23 @@ class ClientLifecycleManager { } /** + * Similar to {@link #scheduleTransactionItem}, but is called without WM lock. + * + * @see WindowProcessController#setReportedProcState(int) + */ + void scheduleTransactionItemUnlocked(@NonNull IApplicationThread client, + @NonNull ClientTransactionItem transactionItem) throws RemoteException { + // Immediately dispatching to client, and must not access WMS. + final ClientTransaction clientTransaction = ClientTransaction.obtain(client); + if (transactionItem.isActivityLifecycleItem()) { + clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem); + } else { + clientTransaction.addCallback(transactionItem); + } + scheduleTransaction(clientTransaction); + } + + /** * Schedules a single transaction item, either a callback or a lifecycle request, delivery to * client application. * @throws RemoteException @@ -65,6 +82,7 @@ class ClientLifecycleManager { */ void scheduleTransactionItem(@NonNull IApplicationThread client, @NonNull ClientTransactionItem transactionItem) throws RemoteException { + // TODO(b/260873529): queue the transaction items. final ClientTransaction clientTransaction = ClientTransaction.obtain(client); if (transactionItem.isActivityLifecycleItem()) { clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem); @@ -82,6 +100,7 @@ class ClientLifecycleManager { void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client, @NonNull ClientTransactionItem transactionItem, @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException { + // TODO(b/260873529): replace with #scheduleTransactionItem after launch for cleanup. final ClientTransaction clientTransaction = ClientTransaction.obtain(client); clientTransaction.addCallback(transactionItem); clientTransaction.setLifecycleStateRequest(lifecycleItem); diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index d0d7f493b969..e6bbd52807bd 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -101,10 +101,8 @@ class InsetsPolicy { mPolicy = displayContent.getDisplayPolicy(); final Resources r = mPolicy.getContext().getResources(); mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard); - mTransientControlTarget = new ControlTarget( - stateController, displayContent.mWmService.mH, "TransientControlTarget"); - mPermanentControlTarget = new ControlTarget( - stateController, displayContent.mWmService.mH, "PermanentControlTarget"); + mTransientControlTarget = new ControlTarget(displayContent, "TransientControlTarget"); + mPermanentControlTarget = new ControlTarget(displayContent, "PermanentControlTarget"); } /** Updates the target which can control system bars. */ @@ -699,24 +697,35 @@ class InsetsPolicy { } } - private static class ControlTarget implements InsetsControlTarget { + private static class ControlTarget implements InsetsControlTarget, Runnable { + private final Handler mHandler; + private final Object mGlobalLock; private final InsetsState mState = new InsetsState(); - private final InsetsController mInsetsController; private final InsetsStateController mStateController; + private final InsetsController mInsetsController; private final String mName; - ControlTarget(InsetsStateController stateController, Handler handler, String name) { - mStateController = stateController; - mInsetsController = new InsetsController(new Host(handler, name)); + ControlTarget(DisplayContent displayContent, String name) { + mHandler = displayContent.mWmService.mH; + mGlobalLock = displayContent.mWmService.mGlobalLock; + mStateController = displayContent.getInsetsStateController(); + mInsetsController = new InsetsController(new Host(mHandler, name)); mName = name; } @Override public void notifyInsetsControlChanged() { - mState.set(mStateController.getRawInsetsState(), true /* copySources */); - mInsetsController.onStateChanged(mState); - mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this)); + mHandler.post(this); + } + + @Override + public void run() { + synchronized (mGlobalLock) { + mState.set(mStateController.getRawInsetsState(), true /* copySources */); + mInsetsController.onStateChanged(mState); + mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this)); + } } @Override diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java index b3a36501b7cf..89a70e502415 100644 --- a/services/core/java/com/android/server/wm/WindowManagerFlags.java +++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java @@ -43,8 +43,6 @@ class WindowManagerFlags { /* Start Available Flags */ - final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag(); - final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag(); final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync(); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 558bf9d6310a..2b18f0775047 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -388,13 +388,22 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio final IApplicationThread thread = mThread; if (prevProcState >= CACHED_CONFIG_PROC_STATE && repProcState < CACHED_CONFIG_PROC_STATE && thread != null && mHasCachedConfiguration) { - final Configuration config; + final ConfigurationChangeItem configurationChangeItem; synchronized (mLastReportedConfiguration) { - config = new Configuration(mLastReportedConfiguration); + onConfigurationChangePreScheduled(mLastReportedConfiguration); + configurationChangeItem = ConfigurationChangeItem.obtain( + mLastReportedConfiguration, mLastTopActivityDeviceId); } // Schedule immediately to make sure the app component (e.g. receiver, service) can get // the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate). - scheduleConfigurationChange(thread, config); + try { + // No WM lock here. + mAtm.getLifecycleManager().scheduleTransactionItemUnlocked( + thread, configurationChangeItem); + } catch (Exception e) { + Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem=" + + configurationChangeItem + " owner=" + mOwner, e); + } } } @@ -1634,11 +1643,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } - scheduleConfigurationChange(thread, config); + onConfigurationChangePreScheduled(config); + scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain( + config, mLastTopActivityDeviceId)); } - private void scheduleConfigurationChange(@NonNull IApplicationThread thread, - @NonNull Configuration config) { + private void onConfigurationChangePreScheduled(@NonNull Configuration config) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName, config); if (Build.IS_DEBUGGABLE && mHasImeService) { @@ -1646,8 +1656,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config); } mHasCachedConfiguration = false; - scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain( - config, mLastTopActivityDeviceId)); } @VisibleForTesting diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp index ccd9bd0a50ca..7edf445d7604 100644 --- a/services/core/jni/com_android_server_hint_HintManagerService.cpp +++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp @@ -20,7 +20,6 @@ #include <aidl/android/hardware/power/IPower.h> #include <android-base/stringprintf.h> -#include <inttypes.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <powermanager/PowerHalController.h> @@ -39,15 +38,6 @@ using android::base::StringPrintf; namespace android { -static struct { - jclass clazz{}; - jfieldID workPeriodStartTimestampNanos{}; - jfieldID actualTotalDurationNanos{}; - jfieldID actualCpuDurationNanos{}; - jfieldID actualGpuDurationNanos{}; - jfieldID timestampNanos{}; -} gWorkDurationInfo; - static power::PowerHalController gPowerHalController; static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap; static std::mutex gSessionMapLock; @@ -190,26 +180,6 @@ static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, ji setMode(session_ptr, static_cast<SessionMode>(mode), enabled); } -static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr, - jobjectArray jWorkDurations) { - int size = env->GetArrayLength(jWorkDurations); - std::vector<WorkDuration> workDurations(size); - for (int i = 0; i < size; i++) { - jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i); - workDurations[i].workPeriodStartTimestampNanos = - env->GetLongField(workDuration, gWorkDurationInfo.workPeriodStartTimestampNanos); - workDurations[i].durationNanos = - env->GetLongField(workDuration, gWorkDurationInfo.actualTotalDurationNanos); - workDurations[i].cpuDurationNanos = - env->GetLongField(workDuration, gWorkDurationInfo.actualCpuDurationNanos); - workDurations[i].gpuDurationNanos = - env->GetLongField(workDuration, gWorkDurationInfo.actualGpuDurationNanos); - workDurations[i].timeStampNanos = - env->GetLongField(workDuration, gWorkDurationInfo.timestampNanos); - } - reportActualWorkDuration(session_ptr, workDurations); -} - // ---------------------------------------------------------------------------- static const JNINativeMethod sHintManagerServiceMethods[] = { /* name, signature, funcPtr */ @@ -224,23 +194,9 @@ static const JNINativeMethod sHintManagerServiceMethods[] = { {"nativeSendHint", "(JI)V", (void*)nativeSendHint}, {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads}, {"nativeSetMode", "(JIZ)V", (void*)nativeSetMode}, - {"nativeReportActualWorkDuration", "(J[Landroid/os/WorkDuration;)V", - (void*)nativeReportActualWorkDuration2}, }; int register_android_server_HintManagerService(JNIEnv* env) { - gWorkDurationInfo.clazz = env->FindClass("android/os/WorkDuration"); - gWorkDurationInfo.workPeriodStartTimestampNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mWorkPeriodStartTimestampNanos", "J"); - gWorkDurationInfo.actualTotalDurationNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mActualTotalDurationNanos", "J"); - gWorkDurationInfo.actualCpuDurationNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mActualCpuDurationNanos", "J"); - gWorkDurationInfo.actualGpuDurationNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mActualGpuDurationNanos", "J"); - gWorkDurationInfo.timestampNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mTimestampNanos", "J"); - return jniRegisterNativeMethods(env, "com/android/server/power/hint/" "HintManagerService$NativeWrapper", diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 0d196b48b3f2..7c539502461b 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -2175,9 +2175,9 @@ class PermissionService(private val service: AccessCheckingService) : userState.appIdDevicePermissionFlags[appId]?.forEachIndexed { _, - deviceId, + persistentDeviceId, devicePermissionFlags -> - println("Permissions (Device $deviceId):") + println("Permissions (Device $persistentDeviceId):") withIndent { devicePermissionFlags.forEachIndexed { _, permissionName, flags -> val isGranted = PermissionFlags.isPermissionGranted(flags) @@ -2658,7 +2658,7 @@ class PermissionService(private val service: AccessCheckingService) : override fun onDevicePermissionFlagsChanged( appId: Int, userId: Int, - deviceId: String, + persistentDeviceId: String, permissionName: String, oldFlags: Int, newFlags: Int @@ -2681,7 +2681,8 @@ class PermissionService(private val service: AccessCheckingService) : permissionName in NOTIFICATIONS_PERMISSIONS && runtimePermissionRevokedUids.get(uid, true) } - runtimePermissionChangedUidDevices.getOrPut(uid) { mutableSetOf() } += deviceId + runtimePermissionChangedUidDevices + .getOrPut(uid) { mutableSetOf() } += persistentDeviceId } if (permission.hasGids && !wasPermissionGranted && isPermissionGranted) { @@ -2695,9 +2696,9 @@ class PermissionService(private val service: AccessCheckingService) : isPermissionFlagsChanged = false } - runtimePermissionChangedUidDevices.forEachIndexed { _, uid, deviceIds -> - deviceIds.forEach { deviceId -> - onPermissionsChangeListeners.onPermissionsChanged(uid, deviceId) + runtimePermissionChangedUidDevices.forEachIndexed { _, uid, persistentDeviceIds -> + persistentDeviceIds.forEach { persistentDeviceId -> + onPermissionsChangeListeners.onPermissionsChanged(uid, persistentDeviceId) } } runtimePermissionChangedUidDevices.clear() @@ -2772,16 +2773,16 @@ class PermissionService(private val service: AccessCheckingService) : when (msg.what) { MSG_ON_PERMISSIONS_CHANGED -> { val uid = msg.arg1 - val deviceId = msg.obj as String - handleOnPermissionsChanged(uid, deviceId) + val persistentDeviceId = msg.obj as String + handleOnPermissionsChanged(uid, persistentDeviceId) } } } - private fun handleOnPermissionsChanged(uid: Int, deviceId: String) { + private fun handleOnPermissionsChanged(uid: Int, persistentDeviceId: String) { listeners.broadcast { listener -> try { - listener.onPermissionsChanged(uid, deviceId) + listener.onPermissionsChanged(uid, persistentDeviceId) } catch (e: RemoteException) { Slog.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e) } @@ -2796,9 +2797,10 @@ class PermissionService(private val service: AccessCheckingService) : listeners.unregister(listener) } - fun onPermissionsChanged(uid: Int, deviceId: String) { + fun onPermissionsChanged(uid: Int, persistentDeviceId: String) { if (listeners.registeredCallbackCount > 0) { - obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0, deviceId).sendToTarget() + obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0, persistentDeviceId) + .sendToTarget() } } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml index 6c24d6d6e5a6..820628c98dee 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml +++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml @@ -21,8 +21,8 @@ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> - <option name="test-file-name" value="FrameworksImeTests.apk" /> <option name="test-file-name" value="SimpleTestIme.apk" /> + <option name="test-file-name" value="FrameworksImeTests.apk" /> </target_preparer> <option name="test-tag" value="FrameworksImeTests" /> diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java index a400f1243afb..eb6e8b4469f0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java @@ -44,6 +44,7 @@ public class DisplayBrightnessStateTest { float brightness = 0.3f; float sdrBrightness = 0.2f; boolean shouldUseAutoBrightness = true; + boolean shouldUpdateScreenBrightnessSetting = true; BrightnessReason brightnessReason = new BrightnessReason(); brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC); brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED); @@ -52,12 +53,15 @@ public class DisplayBrightnessStateTest { .setSdrBrightness(sdrBrightness) .setBrightnessReason(brightnessReason) .setShouldUseAutoBrightness(shouldUseAutoBrightness) + .setShouldUpdateScreenBrightnessSetting(shouldUpdateScreenBrightnessSetting) .build(); assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA); assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA); assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason); assertEquals(displayBrightnessState.getShouldUseAutoBrightness(), shouldUseAutoBrightness); + assertEquals(shouldUpdateScreenBrightnessSetting, + displayBrightnessState.shouldUpdateScreenBrightnessSetting()); assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState)); } @@ -71,6 +75,7 @@ public class DisplayBrightnessStateTest { .setBrightness(0.26f) .setSdrBrightness(0.23f) .setShouldUseAutoBrightness(false) + .setShouldUpdateScreenBrightnessSetting(true) .build(); DisplayBrightnessState state2 = DisplayBrightnessState.Builder.from(state1).build(); assertEquals(state1, state2); @@ -92,7 +97,9 @@ public class DisplayBrightnessStateTest { .append("\n maxBrightness:") .append(displayBrightnessState.getMaxBrightness()) .append("\n customAnimationRate:") - .append(displayBrightnessState.getCustomAnimationRate()); + .append(displayBrightnessState.getCustomAnimationRate()) + .append("\n shouldUpdateScreenBrightnessSetting:") + .append(displayBrightnessState.shouldUpdateScreenBrightnessSetting()); return sb.toString(); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 0bf46547ce1e..353a7bb580ec 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -2754,8 +2754,7 @@ public class DisplayManagerServiceTest { DisplayOffloader mockDisplayOffloader = mock(DisplayOffloader.class); localService.registerDisplayOffloader(displayId, mockDisplayOffloader); - assertThat(display.getDisplayOffloadSessionLocked().getDisplayOffloader()).isEqualTo( - mockDisplayOffloader); + assertThat(display.getDisplayOffloadSessionLocked()).isNotNull(); } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java new file mode 100644 index 000000000000..dea838d3763d --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java @@ -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. + */ + +package com.android.server.display; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class DisplayOffloadSessionImplTest { + + @Mock + private DisplayManagerInternal.DisplayOffloader mDisplayOffloader; + + @Mock + private DisplayPowerControllerInterface mDisplayPowerController; + + private DisplayOffloadSessionImpl mSession; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mDisplayOffloader.startOffload()).thenReturn(true); + mSession = new DisplayOffloadSessionImpl(mDisplayOffloader, mDisplayPowerController); + } + + @Test + public void testStartOffload() { + mSession.startOffload(); + assertTrue(mSession.isActive()); + + // An active session shouldn't be started again + mSession.startOffload(); + verify(mDisplayOffloader, times(1)).startOffload(); + } + + @Test + public void testStopOffload() { + mSession.startOffload(); + mSession.stopOffload(); + + assertFalse(mSession.isActive()); + verify(mDisplayPowerController).setBrightnessFromOffload( + PowerManager.BRIGHTNESS_INVALID_FLOAT); + + // An inactive session shouldn't be stopped again + mSession.stopOffload(); + verify(mDisplayOffloader, times(1)).stopOffload(); + } + + @Test + public void testUpdateBrightness_sessionInactive() { + mSession.updateBrightness(0.3f); + verify(mDisplayPowerController, never()).setBrightnessFromOffload(anyFloat()); + } + + @Test + public void testUpdateBrightness_sessionActive() { + float brightness = 0.3f; + + mSession.startOffload(); + mSession.updateBrightness(brightness); + + verify(mDisplayPowerController).setBrightnessFromOffload(brightness); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index 57f392a5887f..693cafefa2c0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -127,8 +127,6 @@ public final class DisplayPowerController2Test { private Handler mHandler; private DisplayPowerControllerHolder mHolder; private Sensor mProxSensor; - private DisplayManagerInternal.DisplayOffloader mDisplayOffloader; - private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; @Mock private DisplayPowerCallbacks mDisplayPowerCallbacksMock; @@ -148,6 +146,8 @@ public final class DisplayPowerController2Test { private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock; @Mock private DisplayManagerFlags mDisplayManagerFlagsMock; + @Mock + private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; @Captor private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; @@ -1160,19 +1160,19 @@ public final class DisplayPowerController2Test { any(AutomaticBrightnessController.Callbacks.class), any(Looper.class), eq(mSensorManagerMock), - any(), + /* lightSensor= */ any(), eq(mHolder.brightnessMappingStrategy), - anyInt(), - anyFloat(), - anyFloat(), - anyFloat(), - anyInt(), - anyInt(), - anyLong(), - anyLong(), - anyLong(), - anyLong(), - anyBoolean(), + /* lightSensorWarmUpTime= */ anyInt(), + /* brightnessMin= */ anyFloat(), + /* brightnessMax= */ anyFloat(), + /* dozeScaleFactor */ anyFloat(), + /* lightSensorRate= */ anyInt(), + /* initialLightSensorRate= */ anyInt(), + /* brighteningLightDebounceConfig */ anyLong(), + /* darkeningLightDebounceConfig */ anyLong(), + /* brighteningLightDebounceConfigIdle= */ anyLong(), + /* darkeningLightDebounceConfigIdle= */ anyLong(), + /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(), any(HysteresisLevels.class), any(HysteresisLevels.class), any(HysteresisLevels.class), @@ -1180,9 +1180,9 @@ public final class DisplayPowerController2Test { eq(mContext), any(BrightnessRangeController.class), any(BrightnessThrottler.class), - isNull(), - anyInt(), - anyInt(), + /* idleModeBrightnessMapper= */ isNull(), + /* ambientLightHorizonShort= */ anyInt(), + /* ambientLightHorizonLong= */ anyInt(), eq(lux), eq(brightness) ); @@ -1294,9 +1294,8 @@ public final class DisplayPowerController2Test { public void testRampRateForHdrContent_HdrClamperOn() { float clampedBrightness = 0.6f; float transitionRate = 1.5f; - DisplayManagerFlags flags = mock(DisplayManagerFlags.class); - when(flags.isHdrClamperEnabled()).thenReturn(true); - mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags); + when(mDisplayManagerFlagsMock.isHdrClamperEnabled()).thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true); DisplayPowerRequest dpr = new DisplayPowerRequest(); when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); @@ -1392,9 +1391,8 @@ public final class DisplayPowerController2Test { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1) public void testRampMaxTimeInteractiveThenIdle_DifferentValues() { - DisplayManagerFlags flags = mock(DisplayManagerFlags.class); - when(flags.isAdaptiveTone1Enabled()).thenReturn(true); - mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags); + when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true); // Send a display power request DisplayPowerRequest dpr = new DisplayPowerRequest(); @@ -1447,9 +1445,8 @@ public final class DisplayPowerController2Test { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1) public void testRampMaxTimeIdle_DifferentValues() { - DisplayManagerFlags flags = mock(DisplayManagerFlags.class); - when(flags.isAdaptiveTone1Enabled()).thenReturn(true); - mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags); + when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true); // Send a display power request DisplayPowerRequest dpr = new DisplayPowerRequest(); @@ -1481,8 +1478,6 @@ public final class DisplayPowerController2Test { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with DOZE. @@ -1509,8 +1504,6 @@ public final class DisplayPowerController2Test { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with DOZE. @@ -1536,8 +1529,6 @@ public final class DisplayPowerController2Test { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with OFF. @@ -1553,26 +1544,29 @@ public final class DisplayPowerController2Test { verify(mHolder.displayPowerState, never()).setScreenState(anyInt()); } - private void initDisplayOffloadSession() { - mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() { - @Override - public boolean startOffload() { - return true; - } - - @Override - public void stopOffload() {} - }); - - mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() { - @Override - public void setDozeStateOverride(int displayState) {} - - @Override - public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() { - return mDisplayOffloader; - } - }; + @Test + public void testBrightnessFromOffload() { + when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + float brightness = 0.34f; + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness( + any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + + mHolder.dpc.setBrightnessFromOffload(brightness); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + // One triggered by handleBrightnessModeChange, another triggered by + // setBrightnessFromOffload + verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(), + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } /** @@ -1659,12 +1653,6 @@ public final class DisplayPowerController2Test { private DisplayPowerControllerHolder createDisplayPowerController(int displayId, String uniqueId, boolean isEnabled) { - return createDisplayPowerController(displayId, uniqueId, isEnabled, - mock(DisplayManagerFlags.class)); - } - - private DisplayPowerControllerHolder createDisplayPowerController(int displayId, - String uniqueId, boolean isEnabled, DisplayManagerFlags flags) { final DisplayPowerState displayPowerState = mock(DisplayPowerState.class); final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class); final AutomaticBrightnessController automaticBrightnessController = @@ -1690,7 +1678,7 @@ public final class DisplayPowerController2Test { TestInjector injector = spy(new TestInjector(displayPowerState, animator, automaticBrightnessController, wakelockController, brightnessMappingStrategy, hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper, - clamperController, flags)); + clamperController, mDisplayManagerFlagsMock)); final LogicalDisplay display = mock(LogicalDisplay.class); final DisplayDevice device = mock(DisplayDevice.class); @@ -1705,7 +1693,7 @@ public final class DisplayPowerController2Test { mSensorManagerMock, mDisplayBlankerMock, display, mBrightnessTrackerMock, brightnessSetting, () -> { }, - hbmMetadata, /* bootCompleted= */ false, flags); + hbmMetadata, /* bootCompleted= */ false, mDisplayManagerFlagsMock); return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting, animator, automaticBrightnessController, wakelockController, diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 9617bd08fd93..b22799377872 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -125,8 +125,6 @@ public final class DisplayPowerControllerTest { private Handler mHandler; private DisplayPowerControllerHolder mHolder; private Sensor mProxSensor; - private DisplayManagerInternal.DisplayOffloader mDisplayOffloader; - private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; @Mock private DisplayPowerCallbacks mDisplayPowerCallbacksMock; @@ -146,6 +144,8 @@ public final class DisplayPowerControllerTest { private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock; @Mock private DisplayManagerFlags mDisplayManagerFlagsMock; + @Mock + private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; @Captor private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; @@ -1094,19 +1094,19 @@ public final class DisplayPowerControllerTest { any(AutomaticBrightnessController.Callbacks.class), any(Looper.class), eq(mSensorManagerMock), - any(), + /* lightSensor= */ any(), eq(mHolder.brightnessMappingStrategy), - anyInt(), - anyFloat(), - anyFloat(), - anyFloat(), - anyInt(), - anyInt(), - anyLong(), - anyLong(), - anyLong(), - anyLong(), - anyBoolean(), + /* lightSensorWarmUpTime= */ anyInt(), + /* brightnessMin= */ anyFloat(), + /* brightnessMax= */ anyFloat(), + /* dozeScaleFactor */ anyFloat(), + /* lightSensorRate= */ anyInt(), + /* initialLightSensorRate= */ anyInt(), + /* brighteningLightDebounceConfig */ anyLong(), + /* darkeningLightDebounceConfig */ anyLong(), + /* brighteningLightDebounceConfigIdle= */ anyLong(), + /* darkeningLightDebounceConfigIdle= */ anyLong(), + /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(), any(HysteresisLevels.class), any(HysteresisLevels.class), any(HysteresisLevels.class), @@ -1114,9 +1114,9 @@ public final class DisplayPowerControllerTest { eq(mContext), any(BrightnessRangeController.class), any(BrightnessThrottler.class), - isNull(), - anyInt(), - anyInt(), + /* idleModeBrightnessMapper= */ isNull(), + /* ambientLightHorizonShort= */ anyInt(), + /* ambientLightHorizonLong= */ anyInt(), eq(lux), eq(brightness) ); @@ -1386,8 +1386,6 @@ public final class DisplayPowerControllerTest { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with DOZE. @@ -1414,8 +1412,6 @@ public final class DisplayPowerControllerTest { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with DOZE. @@ -1441,8 +1437,6 @@ public final class DisplayPowerControllerTest { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with OFF. @@ -1458,28 +1452,6 @@ public final class DisplayPowerControllerTest { verify(mHolder.displayPowerState, never()).setScreenState(anyInt()); } - private void initDisplayOffloadSession() { - mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() { - @Override - public boolean startOffload() { - return true; - } - - @Override - public void stopOffload() {} - }); - - mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() { - @Override - public void setDozeStateOverride(int displayState) {} - - @Override - public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() { - return mDisplayOffloader; - } - }; - } - private void advanceTime(long timeMs) { mClock.fastForward(timeMs); mTestLooper.dispatchAll(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index a77a9586fb43..8270845657c6 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; @@ -40,12 +41,12 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; -import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.PowerManager; import android.view.Display; import android.view.DisplayAddress; import android.view.SurfaceControl; @@ -118,10 +119,12 @@ public class LocalDisplayAdapterTest { private DisplayNotificationManager mMockedDisplayNotificationManager; @Mock private DisplayManagerFlags mFlags; + @Mock + private DisplayPowerControllerInterface mMockedDisplayPowerController; private Handler mHandler; - private DisplayOffloadSession mDisplayOffloadSession; + private DisplayOffloadSessionImpl mDisplayOffloadSession; private DisplayOffloader mDisplayOffloader; @@ -1195,6 +1198,7 @@ public class LocalDisplayAdapterTest { } verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload(); + assertTrue(mDisplayOffloadSession.isActive()); } @Test @@ -1217,6 +1221,9 @@ public class LocalDisplayAdapterTest { changeStateToDozeRunnable.run(); verify(mDisplayOffloader).stopOffload(); + assertFalse(mDisplayOffloadSession.isActive()); + verify(mMockedDisplayPowerController).setBrightnessFromOffload( + PowerManager.BRIGHTNESS_INVALID_FLOAT); } private void initDisplayOffloadSession() { @@ -1230,15 +1237,8 @@ public class LocalDisplayAdapterTest { public void stopOffload() {} }); - mDisplayOffloadSession = new DisplayOffloadSession() { - @Override - public void setDozeStateOverride(int displayState) {} - - @Override - public DisplayOffloader getDisplayOffloader() { - return mDisplayOffloader; - } - }; + mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader, + mMockedDisplayPowerController); } private void setupCutoutAndRoundedCorners() { diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index c4f483810478..52fa91f5fe0e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -42,7 +42,9 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.display.AutomaticBrightnessController; import com.android.server.display.BrightnessSetting; import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; +import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy; +import com.android.server.display.feature.DisplayManagerFlags; import org.junit.Before; import org.junit.Test; @@ -66,6 +68,8 @@ public final class DisplayBrightnessControllerTest { private BrightnessSetting mBrightnessSetting; @Mock private Runnable mOnBrightnessChangeRunnable; + @Mock + private DisplayManagerFlags mDisplayManagerFlags; @Mock private HandlerExecutor mBrightnessChangeExecutor; @@ -74,7 +78,7 @@ public final class DisplayBrightnessControllerTest { DisplayBrightnessController.Injector() { @Override DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector( - Context context, int displayId) { + Context context, int displayId, DisplayManagerFlags flags) { return mDisplayBrightnessStrategySelector; } }; @@ -92,7 +96,7 @@ public final class DisplayBrightnessControllerTest { .thenReturn(true); mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector, DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable, - mBrightnessChangeExecutor); + mBrightnessChangeExecutor, mDisplayManagerFlags); } @Test @@ -350,7 +354,7 @@ public final class DisplayBrightnessControllerTest { int nonDefaultDisplayId = 1; mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector, nonDefaultDisplayId, DEFAULT_BRIGHTNESS, mBrightnessSetting, - mOnBrightnessChangeRunnable, mBrightnessChangeExecutor); + mOnBrightnessChangeRunnable, mBrightnessChangeExecutor, mDisplayManagerFlags); brightness = 0.5f; when(mBrightnessSetting.getBrightness()).thenReturn(brightness); mDisplayBrightnessController.setAutomaticBrightnessController( @@ -384,4 +388,14 @@ public final class DisplayBrightnessControllerTest { verify(mBrightnessSetting).setBrightness(brightnessValue2); verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2); } + + @Test + public void setBrightnessFromOffload() { + float brightness = 0.4f; + OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class); + when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn( + offloadBrightnessStrategy); + mDisplayBrightnessController.setBrightnessFromOffload(brightness); + verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index 8497dabba67d..37958faed1ca 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -17,6 +17,7 @@ package com.android.server.display.brightness; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -35,13 +36,16 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; +import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.brightness.strategy.BoostBrightnessStrategy; import com.android.server.display.brightness.strategy.DozeBrightnessStrategy; import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy; import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy; +import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy; import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy; import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy; +import com.android.server.display.feature.DisplayManagerFlags; import org.junit.Before; import org.junit.Rule; @@ -71,10 +75,64 @@ public final class DisplayBrightnessStrategySelectorTest { @Mock private FollowerBrightnessStrategy mFollowerBrightnessStrategy; @Mock + private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy; + @Mock + private OffloadBrightnessStrategy mOffloadBrightnessStrategy; + @Mock private Resources mResources; + @Mock + private DisplayManagerFlags mDisplayManagerFlags; private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector; private Context mContext; + private DisplayBrightnessStrategySelector.Injector mInjector = + new DisplayBrightnessStrategySelector.Injector() { + @Override + ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() { + return mScreenOffBrightnessModeStrategy; + } + + @Override + DozeBrightnessStrategy getDozeBrightnessStrategy() { + return mDozeBrightnessModeStrategy; + } + + @Override + OverrideBrightnessStrategy getOverrideBrightnessStrategy() { + return mOverrideBrightnessStrategy; + } + + @Override + TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() { + return mTemporaryBrightnessStrategy; + } + + @Override + BoostBrightnessStrategy getBoostBrightnessStrategy() { + return mBoostBrightnessStrategy; + } + + @Override + FollowerBrightnessStrategy getFollowerBrightnessStrategy(int displayId) { + return mFollowerBrightnessStrategy; + } + + @Override + InvalidBrightnessStrategy getInvalidBrightnessStrategy() { + return mInvalidBrightnessStrategy; + } + + @Override + AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, + int displayId) { + return mAutomaticBrightnessStrategy; + } + + @Override + OffloadBrightnessStrategy getOffloadBrightnessStrategy() { + return mOffloadBrightnessStrategy; + } + }; @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); @@ -87,45 +145,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mContext.getContentResolver()).thenReturn(contentResolver); when(mContext.getResources()).thenReturn(mResources); when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy"); - DisplayBrightnessStrategySelector.Injector injector = - new DisplayBrightnessStrategySelector.Injector() { - @Override - ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() { - return mScreenOffBrightnessModeStrategy; - } - - @Override - DozeBrightnessStrategy getDozeBrightnessStrategy() { - return mDozeBrightnessModeStrategy; - } - - @Override - OverrideBrightnessStrategy getOverrideBrightnessStrategy() { - return mOverrideBrightnessStrategy; - } - - @Override - TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() { - return mTemporaryBrightnessStrategy; - } - - @Override - BoostBrightnessStrategy getBoostBrightnessStrategy() { - return mBoostBrightnessStrategy; - } - - @Override - FollowerBrightnessStrategy getFollowerBrightnessStrategy(int displayId) { - return mFollowerBrightnessStrategy; - } - - @Override - InvalidBrightnessStrategy getInvalidBrightnessStrategy() { - return mInvalidBrightnessStrategy; - } - }; mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, - injector, DISPLAY_ID); + mInjector, DISPLAY_ID, mDisplayManagerFlags); } @@ -188,6 +209,7 @@ public final class DisplayBrightnessStrategySelectorTest { displayPowerRequest.screenBrightnessOverride = Float.NaN; when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, Display.STATE_ON), mInvalidBrightnessStrategy); } @@ -200,4 +222,35 @@ public final class DisplayBrightnessStrategySelectorTest { assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, Display.STATE_ON), mFollowerBrightnessStrategy); } + + @Test + public void selectStrategySelectsOffloadStrategyWhenValid() { + when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f); + assertEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy( + displayPowerRequest, Display.STATE_ON)); + } + + @Test + public void selectStrategyDoesNotSelectOffloadStrategyWhenFeatureFlagDisabled() { + when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(false); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f); + assertNotEquals(mOffloadBrightnessStrategy, + mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, + Display.STATE_ON)); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index b652576a75c8..78ec2ff31161 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -188,6 +188,29 @@ public class AutomaticBrightnessStrategyTest { } @Test + public void testAutoBrightnessState_BrightnessReasonIsOffload() { + mAutomaticBrightnessStrategy.setUseAutoBrightness(true); + int targetDisplayState = Display.STATE_ON; + boolean allowAutoBrightnessWhileDozing = false; + int brightnessReason = BrightnessReason.REASON_OFFLOAD; + int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + float lastUserSetBrightness = 0.2f; + boolean userSetBrightnessChanged = true; + mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true); + mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController) + .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, + mBrightnessConfiguration, + lastUserSetBrightness, + userSetBrightnessChanged, 0.5f, + false, policy, true); + assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()); + assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()); + } + + @Test public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() { mAutomaticBrightnessStrategy.setUseAutoBrightness(true); int targetDisplayState = Display.STATE_DOZE; diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java new file mode 100644 index 000000000000..36719af10abb --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.strategy; + +import static org.junit.Assert.assertEquals; + +import android.hardware.display.DisplayManagerInternal; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class OffloadBrightnessStrategyTest { + + private OffloadBrightnessStrategy mOffloadBrightnessStrategy; + + @Before + public void before() { + mOffloadBrightnessStrategy = new OffloadBrightnessStrategy(); + } + + @Test + public void testUpdateBrightnessWhenOffloadBrightnessIsSet() { + DisplayManagerInternal.DisplayPowerRequest + displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest(); + float brightness = 0.2f; + mOffloadBrightnessStrategy.setOffloadScreenBrightness(brightness); + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_OFFLOAD); + DisplayBrightnessState expectedDisplayBrightnessState = + new DisplayBrightnessState.Builder() + .setBrightness(brightness) + .setBrightnessReason(brightnessReason) + .setSdrBrightness(brightness) + .setDisplayBrightnessStrategyName(mOffloadBrightnessStrategy.getName()) + .setShouldUpdateScreenBrightnessSetting(true) + .build(); + DisplayBrightnessState updatedDisplayBrightnessState = + mOffloadBrightnessStrategy.updateBrightness(displayPowerRequest); + assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index a9f5b14fc48d..733a43329478 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.Manifest; import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.ComponentName; @@ -64,6 +65,7 @@ import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserStateImpl; @@ -80,6 +82,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Set; @SmallTest @Presubmit @@ -113,6 +116,8 @@ public class PackageArchiverTest { @Mock private PackageStateInternal mPackageState; @Mock + private PackageStateInternal mCallerPackageState; + @Mock private Bitmap mIcon; private final InstallSource mInstallSource = @@ -154,6 +159,11 @@ public class PackageArchiverTest { mPackageState); when(mComputer.getPackageStateFiltered(eq(INSTALLER_PACKAGE), anyInt(), anyInt())).thenReturn(mock(PackageStateInternal.class)); + when(mComputer.getPackageStateFiltered(eq(CALLER_PACKAGE), anyInt(), anyInt())).thenReturn( + mCallerPackageState); + AndroidPackage androidPackage = mock(AndroidPackage.class); + when(mCallerPackageState.getAndroidPackage()).thenReturn(androidPackage); + when(androidPackage.getRequestedPermissions()).thenReturn(Set.of()); when(mPackageState.getPackageName()).thenReturn(PACKAGE); when(mPackageState.getInstallSource()).thenReturn(mInstallSource); mPackageSetting = createBasicPackageSetting(); @@ -171,6 +181,12 @@ public class PackageArchiverTest { when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager); when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100); + when(mContext.checkCallingOrSelfPermission( + eq(Manifest.permission.REQUEST_INSTALL_PACKAGES))).thenReturn( + PackageManager.PERMISSION_DENIED); + when(mContext.checkCallingOrSelfPermission( + eq(Manifest.permission.REQUEST_DELETE_PACKAGES))).thenReturn( + PackageManager.PERMISSION_DENIED); when(mAppOpsManager.checkOp( eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED), @@ -386,7 +402,7 @@ public class PackageArchiverTest { Exception e = assertThrows( SecurityException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, "different", - UserHandle.CURRENT)); + mIntentSender, UserHandle.CURRENT)); assertThat(e).hasMessageThat().isEqualTo( String.format( "The UID %s of callerPackageName set by the caller doesn't match the " @@ -404,7 +420,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, - UserHandle.CURRENT)); + mIntentSender, UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s not found.", PACKAGE)); @@ -416,7 +432,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, - UserHandle.CURRENT)); + mIntentSender, UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s is not currently archived.", PACKAGE)); @@ -428,7 +444,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, - UserHandle.CURRENT)); + mIntentSender, UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s is not currently archived.", PACKAGE)); @@ -452,7 +468,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, - UserHandle.CURRENT)); + mIntentSender, UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("No installer found to unarchive app %s.", PACKAGE)); @@ -462,7 +478,8 @@ public class PackageArchiverTest { public void unarchiveApp_success() { mUserState.setArchiveState(createArchiveState()).setInstalled(false); - mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT); + mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, mIntentSender, + UserHandle.CURRENT); rule.mocks().getHandler().flush(); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java index 2d760a7dabac..1002fba3d60d 100644 --- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java @@ -220,7 +220,7 @@ public class PinnerServiceTest { private void setDeviceConfigPinnedAnonSize(long size) { mFakeDeviceConfigInterface.setProperty( - DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, + DeviceConfig.NAMESPACE_RUNTIME_NATIVE, "pin_shared_anon_size", String.valueOf(size), /*makeDefault=*/false); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index 0f3daec263e0..74eb79d7554c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -28,6 +28,8 @@ import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUT import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -248,6 +250,45 @@ public class AuthSessionTest { } @Test + public void testOnErrorReceivedBeforeOnDialogAnimatedIn() throws RemoteException { + final int fingerprintId = 0; + final int faceId = 1; + setupFingerprint(fingerprintId, FingerprintSensorProperties.TYPE_REAR); + setupFace(faceId, true /* confirmationAlwaysRequired */, + mock(IBiometricAuthenticator.class)); + final AuthSession session = createAuthSession(mSensors, + false /* checkDevicePolicyManager */, + Authenticators.BIOMETRIC_STRONG, + TEST_REQUEST_ID, + 0 /* operationId */, + 0 /* userId */); + session.goToInitialState(); + + for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) { + assertThat(sensor.getSensorState()).isEqualTo(BiometricSensor.STATE_WAITING_FOR_COOKIE); + session.onCookieReceived( + session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie()); + } + assertThat(session.allCookiesReceived()).isTrue(); + assertThat(session.getState()).isEqualTo(STATE_AUTH_STARTED); + + final BiometricSensor faceSensor = session.mPreAuthInfo.eligibleSensors.get(faceId); + final BiometricSensor fingerprintSensor = session.mPreAuthInfo.eligibleSensors.get( + fingerprintId); + final int cookie = faceSensor.getCookie(); + session.onErrorReceived(0, cookie, BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL, 0); + + assertThat(faceSensor.getSensorState()).isEqualTo(BiometricSensor.STATE_STOPPED); + assertThat(session.getState()).isEqualTo(STATE_ERROR_PENDING_SYSUI); + + session.onDialogAnimatedIn(true); + + assertThat(session.getState()).isEqualTo(STATE_AUTH_STARTED_UI_SHOWING); + assertThat(fingerprintSensor.getSensorState()).isEqualTo( + BiometricSensor.STATE_AUTHENTICATING); + } + + @Test public void testCancelReducesAppetiteForCookies() throws Exception { setupFace(0 /* id */, false /* confirmationAlwaysRequired */, mock(IBiometricAuthenticator.class)); 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 d87b8d1cb60e..b5ba322b1a5e 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 @@ -773,6 +773,30 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void getAllPersistentDeviceIds_respectsCurrentAssociations() { + mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo)); + TestableLooper.get(this).processAllMessages(); + + assertThat(mLocalService.getAllPersistentDeviceIds()) + .containsExactly(mDeviceImpl.getPersistentDeviceId()); + + 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(); + + assertThat(mLocalService.getAllPersistentDeviceIds()).containsExactly( + VirtualDeviceImpl.createPersistentDeviceId(2), + VirtualDeviceImpl.createPersistentDeviceId(3)); + + mVdms.onCdmAssociationsChanged(Collections.emptyList()); + TestableLooper.get(this).processAllMessages(); + + assertThat(mLocalService.getAllPersistentDeviceIds()).isEmpty(); + } + + @Test public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() { ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2)); mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener); diff --git a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java index 1e73a45a0c22..5aef7a320930 100644 --- a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java @@ -90,7 +90,7 @@ public class AudioPoliciesDeviceRouteControllerTest { @Test public void getDeviceRoute_noSelectedRoutes_returnsDefaultDevice() { - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DEFAULT); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); @@ -105,7 +105,7 @@ public class AudioPoliciesDeviceRouteControllerTest { audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; callAudioRoutesObserver(audioRoutesInfo); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); } @@ -117,7 +117,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.selectRoute(MediaRoute2Info.TYPE_DOCK); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); } @@ -135,7 +135,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.selectRoute(MediaRoute2Info.TYPE_DOCK); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); } @@ -155,7 +155,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.selectRoute(null); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); } @@ -171,7 +171,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); } @@ -202,7 +202,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.updateVolume(VOLUME_SAMPLE_1); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1); } @@ -222,7 +222,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.selectRoute(MediaRoute2Info.TYPE_DOCK); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1); } diff --git a/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java index 24e48517f280..aed68a5dc7b5 100644 --- a/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java @@ -104,7 +104,7 @@ public class LegacyDeviceRouteControllerTest { mOnDeviceRouteChangedListener ); - MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME)) @@ -129,7 +129,7 @@ public class LegacyDeviceRouteControllerTest { mOnDeviceRouteChangedListener ); - MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME)) @@ -243,7 +243,7 @@ public class LegacyDeviceRouteControllerTest { mOnDeviceRouteChangedListener ); - MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getType()).isEqualTo(mExpectedRouteType); assertThat(TextUtils.equals(actualMediaRoute.getName(), mExpectedRouteNameValue)) @@ -310,7 +310,7 @@ public class LegacyDeviceRouteControllerTest { // Simulating wired device being connected. callAudioRoutesObserver(audioRoutesInfo); - MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_HEADPHONES_NAME)) @@ -324,7 +324,7 @@ public class LegacyDeviceRouteControllerTest { AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute(); callAudioRoutesObserver(fakeBluetoothAudioRoute); - MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME)) @@ -334,12 +334,12 @@ public class LegacyDeviceRouteControllerTest { @Test public void updateVolume_differentValue_updatesDeviceRouteVolume() { - MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE); assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue(); - actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + actualMediaRoute = mDeviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_VALUE_SAMPLE_1); } diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index 37485275dac7..d09aa89179b8 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -44,7 +44,6 @@ import android.os.IBinder; import android.os.IHintSession; import android.os.PerformanceHintManager; import android.os.Process; -import android.os.WorkDuration; import android.util.Log; import com.android.server.FgThread; @@ -90,11 +89,6 @@ public class HintManagerServiceTest { private static final long[] DURATIONS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L}; - private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] { - new WorkDuration(1L, 11L, 8L, 4L, 1L), - new WorkDuration(2L, 13L, 8L, 6L, 2L), - new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L), - }; @Mock private Context mContext; @Mock private HintManagerService.NativeWrapper mNativeWrapperMock; @@ -599,55 +593,4 @@ public class HintManagerServiceTest { } a.close(); } - - @Test - public void testReportActualWorkDuration2() throws Exception { - HintManagerService service = createService(); - IBinder token = new Binder(); - - AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); - - a.updateTargetWorkDuration(100L); - a.reportActualWorkDuration2(WORK_DURATIONS_THREE); - verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(), - eq(WORK_DURATIONS_THREE)); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(0L, 11L, 8L, 4L, 1L)}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 0L, 8L, 4L, 1L)}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2( - new WorkDuration[] {new WorkDuration(1L, 11L, 8L, -1L, 1L)}); - }); - - reset(mNativeWrapperMock); - // Set session to background, then the duration would not be updated. - service.mUidObserver.onUidStateChanged( - a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); - - // Using CountDownLatch to ensure above onUidStateChanged() job was digested. - final CountDownLatch latch = new CountDownLatch(1); - FgThread.getHandler().post(() -> { - latch.countDown(); - }); - latch.await(); - - assertFalse(service.mUidObserver.isUidForeground(a.mUid)); - a.reportActualWorkDuration2(WORK_DURATIONS_THREE); - verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); - } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java index 4b6183dc9ffa..8bc027d50a3b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; @@ -39,6 +40,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.UiServiceTestCase; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -55,6 +57,9 @@ import java.util.concurrent.atomic.AtomicBoolean; @SmallTest @RunWith(AndroidJUnit4.class) public class ArchiveTest extends UiServiceTestCase { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final int SIZE = 5; private NotificationManagerService.Archive mArchive; @@ -249,4 +254,29 @@ public class ArchiveTest extends UiServiceTestCase { assertThat(expected).contains(sbn.getKey()); } } + + @Test + public void testRemoveNotificationsByPackage() { + List<String> expected = new ArrayList<>(); + + StatusBarNotification sbn_remove = getNotification("pkg_remove", 0, + UserHandle.of(USER_CURRENT)); + mArchive.record(sbn_remove, REASON_CANCEL); + + StatusBarNotification sbn_keep = getNotification("pkg_keep", 1, + UserHandle.of(USER_CURRENT)); + mArchive.record(sbn_keep, REASON_CANCEL); + expected.add(sbn_keep.getKey()); + + StatusBarNotification sbn_remove2 = getNotification("pkg_remove", 2, + UserHandle.of(USER_CURRENT)); + mArchive.record(sbn_remove2, REASON_CANCEL); + + mArchive.removePackageNotifications("pkg_remove", USER_CURRENT); + List<StatusBarNotification> actual = Arrays.asList(mArchive.getArray(mUm, SIZE, true)); + assertThat(actual).hasSize(expected.size()); + for (StatusBarNotification sbn : actual) { + assertThat(expected).contains(sbn.getKey()); + } + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 7fb8b30dea06..b45dcd4d5403 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -65,6 +65,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.P; +import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; @@ -72,7 +73,6 @@ import static android.os.UserHandle.USER_SYSTEM; import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; -import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; @@ -87,8 +87,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; -import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE; -import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; @@ -309,7 +307,6 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; - @SmallTest @RunWith(AndroidTestingRunner.class) @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. @@ -739,9 +736,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { clearInvocations(mRankingHandler); when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - mTestFlagResolver.setFlagOverride(FSI_FORCE_DEMOTE, false); - mTestFlagResolver.setFlagOverride(SHOW_STICKY_HUN_FOR_DENIED_FSI, false); - var checker = mock(TestableNotificationManagerService.ComponentPermissionChecker.class); mService.permissionChecker = checker; when(checker.check(anyString(), anyInt(), anyInt(), anyBoolean())) @@ -818,6 +812,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageIntentReceiver.onReceive(getContext(), intent); } + private void simulatePackageRemovedBroadcast(String pkg, int uid) { + // mimics receive broadcast that package is removed, but doesn't remove the package. + final Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, + new String[]{pkg}); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid}); + + final Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED); + intent.setData(Uri.parse("package:" + pkg)); + intent.putExtras(extras); + + mPackageIntentReceiver.onReceive(getContext(), intent); + } + private void simulatePackageDistractionBroadcast(int flag, String[] pkgs, int[] uids) { // mimics receive broadcast that package is (un)distracting // but does not actually register that info with packagemanager @@ -883,6 +891,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mTestNotificationChannel.setAllowBubbles(channelEnabled); } + private void setUpPrefsForHistory(int uid, boolean globalEnabled) { + // Sets NOTIFICATION_HISTORY_ENABLED setting for calling process uid + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, uid); + // Sets NOTIFICATION_HISTORY_ENABLED setting for uid 0 + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0); + + // Forces an update by calling observe on mSettingsObserver, which picks up the settings + // changes above. + mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper); + + assertEquals(globalEnabled, Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0 /* =def */, uid) != 0); + } + private StatusBarNotification generateSbn(String pkg, int uid, long postTime, int userId) { Notification.Builder nb = new Notification.Builder(mContext, "a") .setContentTitle("foo") @@ -9836,6 +9860,43 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testHandleOnPackageRemoved_ClearsHistory() throws RemoteException { + // Enables Notification History setting + setUpPrefsForHistory(mUid, true /* =enabled */); + + // Posts a notification to the mTestNotificationChannel. + final NotificationRecord notif = generateNotificationRecord( + mTestNotificationChannel, 1, null, false); + mService.addNotification(notif); + StatusBarNotification[] notifs = mBinderService.getActiveNotifications( + notif.getSbn().getPackageName()); + assertEquals(1, notifs.length); + + // Cancels all notifications. + mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, + notif.getUserId(), REASON_CANCEL); + waitForIdle(); + notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); + assertEquals(0, notifs.length); + + // Checks that notification history's recently canceled archive contains the notification. + notifs = mBinderService.getHistoricalNotificationsWithAttribution(PKG, + mContext.getAttributionTag(), 5 /* count */, false /* includeSnoozed */); + waitForIdle(); + assertEquals(1, notifs.length); + + // Remove sthe package that contained the channel + simulatePackageRemovedBroadcast(PKG, mUid); + waitForIdle(); + + // Checks that notification history no longer contains the notification. + notifs = mBinderService.getHistoricalNotificationsWithAttribution( + PKG, mContext.getAttributionTag(), 5 /* count */, false /* includeSnoozed */); + waitForIdle(); + assertEquals(0, notifs.length); + } + + @Test public void testNotificationHistory_addNoisyNotification() throws Exception { NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, null /* tvExtender */); @@ -11472,14 +11533,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class)); } - private void verifyStickyHun(Flag flag, int permissionState, boolean appRequested, + private void verifyStickyHun(int permissionState, boolean appRequested, boolean isSticky) throws Exception { when(mPermissionHelper.hasRequestedPermission(Manifest.permission.USE_FULL_SCREEN_INTENT, PKG, mUserId)).thenReturn(appRequested); - mTestFlagResolver.setFlagOverride(flag, true); - when(mPermissionManager.checkPermissionForDataDelivery( eq(Manifest.permission.USE_FULL_SCREEN_INTENT), any(), any())) .thenReturn(permissionState); @@ -11503,8 +11562,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testFixNotification_flagEnableStickyHun_fsiPermissionHardDenied_showStickyHun() throws Exception { - verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI, - /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true, + verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true, /* isSticky= */ true); } @@ -11512,16 +11570,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testFixNotification_flagEnableStickyHun_fsiPermissionSoftDenied_showStickyHun() throws Exception { - verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI, - /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true, + verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true, /* isSticky= */ true); } @Test public void testFixNotification_fsiPermissionSoftDenied_appNotRequest_noShowStickyHun() throws Exception { - verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI, - /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false, + verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false, /* isSticky= */ false); } @@ -11530,39 +11586,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testFixNotification_flagEnableStickyHun_fsiPermissionGranted_showFsi() throws Exception { - verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI, - /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true, + verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_GRANTED, true, /* isSticky= */ false); } @Test - public void testFixNotification_flagForceStickyHun_fsiPermissionHardDenied_showStickyHun() - throws Exception { - - verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE, - /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true, - /* isSticky= */ true); - } - - @Test - public void testFixNotification_flagForceStickyHun_fsiPermissionSoftDenied_showStickyHun() - throws Exception { - - verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE, - /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true, - /* isSticky= */ true); - } - - @Test - public void testFixNotification_flagForceStickyHun_fsiPermissionGranted_showStickyHun() - throws Exception { - - verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE, - /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true, - /* isSticky= */ true); - } - - @Test public void fixNotification_withFgsFlag_butIsNotFgs() throws Exception { final ApplicationInfo applicationInfo = new ApplicationInfo(); when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java index 5147a08b5216..29848d0139b1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java @@ -171,19 +171,8 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { } @Test - public void testGetFsiState_stickyHunFlagDisabled_zero() { - final int fsiState = NotificationRecordLogger.getFsiState( - /* isStickyHunFlagEnabled= */ false, - /* hasFullScreenIntent= */ true, - /* hasFsiRequestedButDeniedFlag= */ true, - /* eventType= */ NOTIFICATION_POSTED); - assertEquals(0, fsiState); - } - - @Test public void testGetFsiState_isUpdate_zero() { final int fsiState = NotificationRecordLogger.getFsiState( - /* isStickyHunFlagEnabled= */ true, /* hasFullScreenIntent= */ true, /* hasFsiRequestedButDeniedFlag= */ true, /* eventType= */ NOTIFICATION_UPDATED); @@ -193,7 +182,6 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { @Test public void testGetFsiState_hasFsi_allowedEnum() { final int fsiState = NotificationRecordLogger.getFsiState( - /* isStickyHunFlagEnabled= */ true, /* hasFullScreenIntent= */ true, /* hasFsiRequestedButDeniedFlag= */ false, /* eventType= */ NOTIFICATION_POSTED); @@ -203,7 +191,6 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { @Test public void testGetFsiState_fsiPermissionDenied_deniedEnum() { final int fsiState = NotificationRecordLogger.getFsiState( - /* isStickyHunFlagEnabled= */ true, /* hasFullScreenIntent= */ false, /* hasFsiRequestedButDeniedFlag= */ true, /* eventType= */ NOTIFICATION_POSTED); @@ -213,7 +200,6 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { @Test public void testGetFsiState_noFsi_noFsiEnum() { final int fsiState = NotificationRecordLogger.getFsiState( - /* isStickyHunFlagEnabled= */ true, /* hasFullScreenIntent= */ false, /* hasFsiRequestedButDeniedFlag= */ false, /* eventType= */ NOTIFICATION_POSTED); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index c156e376ce3e..fe21103096ca 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -5733,17 +5733,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testGetFsiState_flagDisabled_zero() { - final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0, - /* requestedFsiPermission */ true, /* isFlagEnabled= */ false); - - assertEquals(0, fsiState); - } - - @Test public void testGetFsiState_appDidNotRequest_enumNotRequested() { final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0, - /* requestedFsiPermission */ false, /* isFlagEnabled= */ true); + /* requestedFsiPermission */ false); assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, fsiState); } @@ -5754,7 +5746,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { .thenReturn(PermissionManager.PERMISSION_GRANTED); final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0, - /* requestedFsiPermission= */ true, /* isFlagEnabled= */ true); + /* requestedFsiPermission= */ true); assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, fsiState); } @@ -5765,7 +5757,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { .thenReturn(PermissionManager.PERMISSION_SOFT_DENIED); final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0, - /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true); + /* requestedFsiPermission = */ true); assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState); } @@ -5776,7 +5768,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { .thenReturn(PermissionManager.PERMISSION_HARD_DENIED); final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0, - /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true); + /* requestedFsiPermission = */ true); assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState); } @@ -5785,18 +5777,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testIsFsiPermissionUserSet_appDidNotRequest_false() { final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0, /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, - /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET, - /* isStickyHunFlagEnabled= */ true); - - assertFalse(isUserSet); - } - - @Test - public void testIsFsiPermissionUserSet_flagDisabled_false() { - final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0, - /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, - /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET, - /* isStickyHunFlagEnabled= */ false); + /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET); assertFalse(isUserSet); } @@ -5805,8 +5786,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testIsFsiPermissionUserSet_userSet_true() { final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0, /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, - /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET, - /* isStickyHunFlagEnabled= */ true); + /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET); assertTrue(isUserSet); } @@ -5815,8 +5795,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testIsFsiPermissionUserSet_notUserSet_false() { final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0, /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, - /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET, - /* isStickyHunFlagEnabled= */ true); + /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET); assertFalse(isUserSet); } diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp index 1b8d746f271f..e83f03d155aa 100644 --- a/services/tests/wmtests/Android.bp +++ b/services/tests/wmtests/Android.bp @@ -99,4 +99,7 @@ android_test { enabled: false, }, + data: [ + ":OverlayTestApp", + ], } diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 762e23c8e288..f2a1fe859634 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -119,6 +119,9 @@ </intent-filter> </activity> + <activity android:name="com.android.server.wm.ActivityRecordInputSinkTests$TestActivity" + android:exported="true"> + </activity> </application> <instrumentation diff --git a/services/tests/wmtests/AndroidTest.xml b/services/tests/wmtests/AndroidTest.xml index 2717ef901216..f8ebeaddcb7e 100644 --- a/services/tests/wmtests/AndroidTest.xml +++ b/services/tests/wmtests/AndroidTest.xml @@ -21,6 +21,7 @@ <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> <option name="test-file-name" value="WmTests.apk" /> + <option name="test-file-name" value="OverlayTestApp.apk" /> </target_preparer> <option name="test-tag" value="WmTests" /> diff --git a/services/tests/wmtests/OverlayApp/Android.bp b/services/tests/wmtests/OverlayApp/Android.bp new file mode 100644 index 000000000000..77d5b2295dc9 --- /dev/null +++ b/services/tests/wmtests/OverlayApp/Android.bp @@ -0,0 +1,19 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "OverlayTestApp", + + srcs: ["**/*.java"], + + resource_dirs: ["res"], + + certificate: "platform", + platform_apis: true, +} diff --git a/services/tests/wmtests/OverlayApp/AndroidManifest.xml b/services/tests/wmtests/OverlayApp/AndroidManifest.xml new file mode 100644 index 000000000000..5b4ef577112a --- /dev/null +++ b/services/tests/wmtests/OverlayApp/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.overlay_app"> + <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" /> + + <application> + <activity android:name=".OverlayApp" + android:exported="true" + android:theme="@style/TranslucentFloatingTheme"> + </activity> + </application> +</manifest> diff --git a/services/tests/wmtests/OverlayApp/res/values/styles.xml b/services/tests/wmtests/OverlayApp/res/values/styles.xml new file mode 100644 index 000000000000..fff10a3f29ac --- /dev/null +++ b/services/tests/wmtests/OverlayApp/res/values/styles.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <style name="TranslucentFloatingTheme" > + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowIsFloating">true</item> + <item name="android:windowNoTitle">true</item> + + <!-- Disables starting window. --> + <item name="android:windowDisablePreview">true</item> + </style> +</resources> diff --git a/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java b/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java new file mode 100644 index 000000000000..89161c51dc53 --- /dev/null +++ b/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.overlay_app; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Bundle; +import android.view.Gravity; +import android.view.WindowManager; +import android.widget.LinearLayout; + +/** + * Test app that is translucent not touchable modal. + * If launched with "disableInputSink" extra boolean value, this activity disables + * ActivityRecordInputSinkEnabled as long as the permission is granted. + */ +public class OverlayApp extends Activity { + private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + LinearLayout tv = new LinearLayout(this); + tv.setBackgroundColor(Color.GREEN); + tv.setPadding(50, 50, 50, 50); + tv.setGravity(Gravity.CENTER); + setContentView(tv); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + + if (getIntent().getBooleanExtra(KEY_DISABLE_INPUT_SINK, false)) { + setActivityRecordInputSinkEnabled(false); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java new file mode 100644 index 000000000000..3b280d9d45bd --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java @@ -0,0 +1,209 @@ +/* + * 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.server.wm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.UiAutomation; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.SystemClock; +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.view.MotionEvent; +import android.view.ViewGroup; +import android.widget.Button; +import android.window.WindowInfosListenerForTest; + +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.MediumTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.window.flags.Flags; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Internal variant of {@link android.server.wm.window.ActivityRecordInputSinkTests}. + */ +@MediumTest +@RunWith(AndroidJUnit4.class) +public class ActivityRecordInputSinkTests { + private static final String OVERLAY_APP_PKG = "com.android.server.wm.overlay_app"; + private static final String OVERLAY_ACTIVITY = OVERLAY_APP_PKG + "/.OverlayApp"; + private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink"; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Rule + public final ActivityScenarioRule<TestActivity> mActivityRule = + new ActivityScenarioRule<>(TestActivity.class); + + private UiAutomation mUiAutomation; + + @Before + public void setUp() { + mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); + } + + @After + public void tearDown() { + ActivityManager am = + InstrumentationRegistry.getInstrumentation().getContext().getSystemService( + ActivityManager.class); + mUiAutomation.adoptShellPermissionIdentity(); + try { + am.forceStopPackage(OVERLAY_APP_PKG); + } finally { + mUiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + public void testSimpleButtonPress() { + injectTapOnButton(); + + mActivityRule.getScenario().onActivity(a -> { + assertEquals(1, a.mNumClicked); + }); + } + + @Test + public void testSimpleButtonPress_withOverlay() throws InterruptedException { + startOverlayApp(false); + waitForOverlayApp(); + + injectTapOnButton(); + + mActivityRule.getScenario().onActivity(a -> { + assertEquals(0, a.mNumClicked); + }); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK) + public void testSimpleButtonPress_withOverlayDisableInputSink() throws InterruptedException { + startOverlayApp(true); + waitForOverlayApp(); + + injectTapOnButton(); + + mActivityRule.getScenario().onActivity(a -> { + assertEquals(1, a.mNumClicked); + }); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK) + public void testSimpleButtonPress_withOverlayDisableInputSink_flagDisabled() + throws InterruptedException { + startOverlayApp(true); + waitForOverlayApp(); + + injectTapOnButton(); + + mActivityRule.getScenario().onActivity(a -> { + assertEquals(0, a.mNumClicked); + }); + } + + private void startOverlayApp(boolean disableInputSink) { + String launchCommand = "am start -n " + OVERLAY_ACTIVITY; + if (disableInputSink) { + launchCommand += " --ez " + KEY_DISABLE_INPUT_SINK + " true"; + } + + mUiAutomation.adoptShellPermissionIdentity(); + try { + mUiAutomation.executeShellCommand(launchCommand); + } finally { + mUiAutomation.dropShellPermissionIdentity(); + } + } + + private void waitForOverlayApp() throws InterruptedException { + final var listenerHost = new WindowInfosListenerForTest(); + final var latch = new CountDownLatch(1); + final Consumer<List<WindowInfosListenerForTest.WindowInfo>> listener = windowInfos -> { + final boolean inputSinkReady = windowInfos.stream().anyMatch(info -> + info.isVisible + && info.name.contains("ActivityRecordInputSink " + OVERLAY_ACTIVITY)); + if (inputSinkReady) { + latch.countDown(); + } + }; + + listenerHost.addWindowInfosListener(listener); + try { + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } finally { + listenerHost.removeWindowInfosListener(listener); + } + } + + private void injectTapOnButton() { + Rect buttonBounds = new Rect(); + mActivityRule.getScenario().onActivity(a -> { + a.mButton.getBoundsOnScreen(buttonBounds); + }); + final int x = buttonBounds.centerX(); + final int y = buttonBounds.centerY(); + + MotionEvent down = MotionEvent.obtain(SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y, 0); + mUiAutomation.injectInputEvent(down, true); + + SystemClock.sleep(10); + + MotionEvent up = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), + MotionEvent.ACTION_UP, x, y, 0); + mUiAutomation.injectInputEvent(up, true); + + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + public static class TestActivity extends Activity { + int mNumClicked = 0; + Button mButton; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mButton = new Button(this); + mButton.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + setContentView(mButton); + mButton.setOnClickListener(v -> mNumClicked++); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index e2bb115d5fbd..98055fa6f0d5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -118,6 +118,7 @@ import com.android.compatibility.common.util.DeviceConfigStateHelper; import com.android.internal.util.FrameworkStatsLog; import com.android.server.am.PendingIntentRecord; import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.wm.BackgroundActivityStartController.BalVerdict; import com.android.server.wm.LaunchParamsController.LaunchParamsModifier; import com.android.server.wm.utils.MockTracker; @@ -1378,7 +1379,8 @@ public class ActivityStarterTests extends WindowTestsBase { .setUserId(10) .build(); - final int result = starter.recycleTask(task, null, null, null); + final int result = starter.recycleTask(task, null, null, null, + BalVerdict.ALLOW_BY_DEFAULT); assertThat(result == START_SUCCESS).isTrue(); assertThat(starter.mAddingToTask).isTrue(); } @@ -1892,7 +1894,7 @@ public class ActivityStarterTests extends WindowTestsBase { starter.startActivityInner(target, source, null /* voiceSession */, null /* voiceInteractor */, 0 /* startFlags */, options, inTask, inTaskFragment, - BackgroundActivityStartController.BAL_ALLOW_DEFAULT, null /* intentGrants */, - -1 /* realCallingUid */); + BalVerdict.ALLOW_BY_DEFAULT, + null /* intentGrants */, -1 /* realCallingUid */); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java index cf620fe605c6..c404c77c8550 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.server.wm.BackgroundActivityStartController.BalVerdict; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -186,7 +187,7 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase { /* options */null, /* inTask */null, /* inTaskFragment */ null, - /* balCode */ BackgroundActivityStartController.BAL_ALLOW_DEFAULT, + BalVerdict.ALLOW_BY_DEFAULT, /* intentGrants */null, /* realCaiingUid */ -1); @@ -216,7 +217,7 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase { /* options= */null, /* inTask= */null, /* inTaskFragment= */ null, - /* balCode= */ BackgroundActivityStartController.BAL_ALLOW_DEFAULT, + BalVerdict.ALLOW_BY_DEFAULT, /* intentGrants= */null, /* realCaiingUid */ -1); 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 e152feb141e1..e31ee1174656 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -306,7 +306,7 @@ public class WindowProcessControllerTests extends WindowTestsBase { @Test public void testCachedStateConfigurationChange() throws RemoteException { - doNothing().when(mClientLifecycleManager).scheduleTransactionItem(any(), any()); + doNothing().when(mClientLifecycleManager).scheduleTransactionItemUnlocked(any(), any()); final IApplicationThread thread = mWpc.getThread(); final Configuration newConfig = new Configuration(mWpc.getConfiguration()); newConfig.densityDpi += 100; @@ -322,18 +322,17 @@ public class WindowProcessControllerTests extends WindowTestsBase { newConfig.densityDpi += 100; mWpc.onConfigurationChanged(newConfig); verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any()); + verify(mClientLifecycleManager, never()).scheduleTransactionItemUnlocked(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(mClientLifecycleManager).scheduleTransactionItem(eq(thread), captor.capture()); + verify(mClientLifecycleManager).scheduleTransactionItemUnlocked( + eq(thread), captor.capture()); final ClientTransactionHandler client = mock(ClientTransactionHandler.class); captor.getValue().preExecute(client); - final ArgumentCaptor<Configuration> configCaptor = - ArgumentCaptor.forClass(Configuration.class); - verify(client).updatePendingConfiguration(configCaptor.capture()); - assertEquals(newConfig, configCaptor.getValue()); + verify(client).updatePendingConfiguration(newConfig); } @Test diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 63de41f80c23..4c56f33af430 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1953,6 +1953,13 @@ public class UsageStatsService extends SystemService implements } } + // Flags status. + pw.println("Flags:"); + pw.println(" " + Flags.FLAG_USER_INTERACTION_TYPE_API + + ": " + Flags.userInteractionTypeApi()); + pw.println(" " + Flags.FLAG_USE_PARCELED_LIST + + ": " + Flags.useParceledList()); + final int[] userIds; synchronized (mLock) { final int userCount = mUserState.size(); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index a584fc9b2216..b77596391be1 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -2437,8 +2437,6 @@ public class VoiceInteractionManagerService extends SystemService { synchronized (VoiceInteractionManagerServiceStub.this) { Slog.i(TAG, "Force stopping current voice recognizer: " + getCurRecognizer(userHandle)); - // TODO: Figure out why the interactor was being cleared and document it. - setCurInteractor(null, userHandle); initRecognizer(userHandle); } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index f8608b8fead2..e12a815a84f5 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -23,6 +23,7 @@ import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; import android.annotation.DurationMillisLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -70,6 +71,7 @@ import android.util.Pair; import com.android.internal.telephony.ISetOpportunisticDataCallback; import com.android.internal.telephony.ISub; import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.util.HandlerExecutor; import com.android.internal.util.FunctionalUtils; import com.android.internal.util.Preconditions; @@ -95,7 +97,22 @@ import java.util.function.Consumer; import java.util.stream.Collectors; /** - * Subscription manager provides the mobile subscription information. + * Subscription manager provides the mobile subscription information that are associated with the + * calling user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U) + * and below can see all subscriptions as it does today. + * + * <p>For example, if we have + * <ul> + * <li> Subscription 1 associated with personal profile. + * <li> Subscription 2 associated with work profile. + * </ul> + * Then for SDK 35+, if the caller identity is personal profile, then + * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa. + * + * <p>If the caller needs to see all subscriptions across user profiles, + * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission + * may be required as documented on the each API. + * */ @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) @@ -1446,6 +1463,16 @@ public class SubscriptionManager { } } + /** + * {@code true} if the SubscriptionManager instance should see all subscriptions regardless its + * association with particular user profile. + * + * <p> It only applies to Android SDK 35(V) and above. For Android SDK 34(U) and below, the + * caller can see all subscription across user profiles as it does today today even if it's + * {@code false}. + */ + private boolean mIsForAllUserProfiles = false; + /** @hide */ @UnsupportedAppUsage public SubscriptionManager(Context context) { @@ -1776,8 +1803,23 @@ public class SubscriptionManager { } /** - * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted - * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}. + * Get the SubscriptionInfo(s) of the currently active SIM(s) associated with the current caller + * user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U) + * and below can see all subscriptions as it does today. + * + * <p>For example, if we have + * <ul> + * <li> Subscription 1 associated with personal profile. + * <li> Subscription 2 associated with work profile. + * </ul> + * Then for SDK 35+, if the caller identity is personal profile, then this will return + * subscription 1 only and vice versa. + * + * <p>If the caller needs to see all subscriptions across user profiles, + * use {@link #createForAllUserProfiles} to convert this instance to see all. + * + * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by + * {@link SubscriptionInfo#getSubscriptionId}. * * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} * or that the calling app has carrier privileges (see @@ -1800,8 +1842,25 @@ public class SubscriptionManager { * </ul> */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + // @RequiresPermission(TODO(b/308809058)) public List<SubscriptionInfo> getActiveSubscriptionInfoList() { - return getActiveSubscriptionInfoList(/* userVisibleonly */true); + List<SubscriptionInfo> activeList = null; + + try { + ISub iSub = TelephonyManager.getSubscriptionService(); + if (iSub != null) { + activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(), + mContext.getAttributionTag(), mIsForAllUserProfiles); + } + } catch (RemoteException ex) { + // ignore it + } + + if (activeList != null) { + activeList = activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo)) + .collect(Collectors.toList()); + } + return activeList; } /** @@ -1835,6 +1894,26 @@ public class SubscriptionManager { } /** + * Convert this subscription manager instance into one that can see all subscriptions across + * user profiles. + * + * @return a SubscriptionManager that can see all subscriptions regardless its user profile + * association. + * + * @see #getActiveSubscriptionInfoList + * @see #getActiveSubscriptionInfoCount + * @see UserHandle + */ + @FlaggedApi(Flags.FLAG_WORK_PROFILE_API_SPLIT) + // @RequiresPermission(TODO(b/308809058)) + // The permission check for accessing all subscriptions will be enforced upon calling the + // individual APIs linked above. + @NonNull public SubscriptionManager createForAllUserProfiles() { + mIsForAllUserProfiles = true; + return this; + } + + /** * This is similar to {@link #getActiveSubscriptionInfoList()}, but if userVisibleOnly * is true, it will filter out the hidden subscriptions. * @@ -1847,7 +1926,7 @@ public class SubscriptionManager { ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(), - mContext.getAttributionTag()); + mContext.getAttributionTag(), true /*isForAllUserProfiles*/); } } catch (RemoteException ex) { // ignore it @@ -2002,13 +2081,19 @@ public class SubscriptionManager { } /** - * Get the active subscription count. + * Get the active subscription count associated with the current caller user profile for + * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as + * it does today. + * + * <p>If the caller needs to see all subscriptions across user profiles, + * use {@link #createForAllUserProfiles} to convert this instance to see all. * * @return The current number of active subscriptions. * * @see #getActiveSubscriptionInfoList() */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + // @RequiresPermission(TODO(b/308809058)) public int getActiveSubscriptionInfoCount() { int result = 0; @@ -2016,7 +2101,7 @@ public class SubscriptionManager { ISub iSub = TelephonyManager.getSubscriptionService(); if (iSub != null) { result = iSub.getActiveSubInfoCount(mContext.getOpPackageName(), - mContext.getAttributionTag()); + mContext.getAttributionTag(), mIsForAllUserProfiles); } } catch (RemoteException ex) { // ignore it diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index d2dbeb7aff74..3f41d5667107 100644 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -66,6 +66,8 @@ interface ISub { * * @param callingPackage The package maing the call. * @param callingFeatureId The feature in the package + * @param isForAllProfiles whether the caller intends to see all subscriptions regardless + * association. * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device. * <ul> * <li> @@ -83,14 +85,17 @@ interface ISub { * </ul> */ List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage, - String callingFeatureId); + String callingFeatureId, boolean isForAllProfiles); /** * @param callingPackage The package making the call. * @param callingFeatureId The feature in the package. + * @param isForAllProfile whether the caller intends to see all subscriptions regardless + * association. * @return the number of active subscriptions */ - int getActiveSubInfoCount(String callingPackage, String callingFeatureId); + int getActiveSubInfoCount(String callingPackage, String callingFeatureId, + boolean isForAllProfile); /** * @return the maximum number of subscriptions this device will support at any one time. diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt index b44f1a607b05..c49f8fecfdee 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -23,10 +23,13 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_FINISH_ACTIVITY import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -53,7 +56,12 @@ class CloseImeToHomeOnFinishActivityTest(flicker: LegacyFlickerTest) : BaseTest( testApp.launchViaIntent(wmHelper) testApp.openIME(wmHelper) } - transitions { testApp.finishActivity(wmHelper) } + transitions { + broadcastActionTrigger.doAction(ACTION_FINISH_ACTIVITY) + wmHelper.StateSyncBuilder() + .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent()) + .waitForAndVerify() + } teardown { simpleApp.exit(wmHelper) } } diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt index 976ac82c8bb5..994edc592f5d 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt @@ -28,6 +28,7 @@ import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_TOGGLE_ORIENTATION import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -53,7 +54,11 @@ class OpenImeWindowToFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseTest // Enable letterbox when the app calls setRequestedOrientation device.executeShellCommand("cmd window set-ignore-orientation-request true") } - transitions { testApp.toggleFixPortraitOrientation(wmHelper) } + transitions { + broadcastActionTrigger.doAction(ACTION_TOGGLE_ORIENTATION) + // Ensure app relaunching transition finished and the IME was shown + testApp.waitIMEShown(wmHelper) + } teardown { testApp.exit() device.executeShellCommand("cmd window set-ignore-orientation-request false") diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt index aff8e657bec0..6ee5a9a775b3 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt @@ -104,7 +104,7 @@ class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker: LegacyFlicke @Presubmit @Test - open fun imeLayerIsVisibleWhenSwitchingToImeApp() { + fun imeLayerIsVisibleWhenSwitchingToImeApp() { flicker.assertLayersStart { isVisible(ComponentNameMatcher.IME) } flicker.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) } flicker.assertLayersEnd { isVisible(ComponentNameMatcher.IME) } diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt index 4ffdcea455ba..1ad5c0de282b 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt @@ -93,7 +93,7 @@ class ShowImeOnAppStartWhenLaunchingAppTest(flicker: LegacyFlickerTest) : BaseTe } transitions { testApp.launchViaIntent(wmHelper) - wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify() + testApp.waitIMEShown(wmHelper) } } diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt index 6ad235ce892e..181a2a229940 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt @@ -23,11 +23,14 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import android.tools.device.traces.parsers.toFlickerComponent import android.view.WindowInsets.Type.ime import android.view.WindowInsets.Type.navigationBars import android.view.WindowInsets.Type.statusBars import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_START_DIALOG_THEMED_ACTIVITY import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.FixMethodOrder @@ -50,8 +53,12 @@ class ShowImeWhileDismissingThemedPopupDialogTest(flicker: LegacyFlickerTest) : override val transition: FlickerBuilder.() -> Unit = { setup { testApp.launchViaIntent(wmHelper) - wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify() - testApp.startDialogThemedActivity(wmHelper) + testApp.waitIMEShown(wmHelper) + broadcastActionTrigger.doAction(ACTION_START_DIALOG_THEMED_ACTIVITY) + wmHelper.StateSyncBuilder() + .withFullScreenApp( + ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent()) + .waitForAndVerify() // Verify IME insets isn't visible on dialog since it's non-IME focusable window assertFalse(testApp.getInsetsVisibleFromDialog(ime())) assertTrue(testApp.getInsetsVisibleFromDialog(statusBars())) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt index 7c9c05d7da85..ad272a052220 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker import android.app.Instrumentation +import android.content.Intent import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerBuilderProvider @@ -50,6 +51,19 @@ constructor( /** Specification of the test transition to execute */ abstract val transition: FlickerBuilder.() -> Unit + protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) + + // Helper class to process test actions by broadcast. + protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) { + private fun createIntentWithAction(broadcastAction: String): Intent { + return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND) + } + + fun doAction(broadcastAction: String) { + instrumentation.context.sendBroadcast(createIntentWithAction(broadcastAction)) + } + } + /** * Entry point for the test runner. It will use this method to initialize and cache flicker * executions diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt index 252f7d3e1bed..cb1aab0bfeea 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt @@ -50,7 +50,7 @@ constructor( waitIMEShown(wmHelper) } - protected fun waitIMEShown(wmHelper: WindowManagerStateHelper) { + fun waitIMEShown(wmHelper: WindowManagerStateHelper) { wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify() } @@ -63,17 +63,4 @@ constructor( uiDevice.pressBack() wmHelper.StateSyncBuilder().withImeGone().waitForAndVerify() } - - open fun finishActivity(wmHelper: WindowManagerStateHelper) { - val finishButton = - uiDevice.wait( - Until.findObject(By.res(packageName, "finish_activity_btn")), - FIND_TIMEOUT - ) - requireNotNull(finishButton) { - "Finish activity button not found, probably IME activity is not on the screen?" - } - finishButton.click() - wmHelper.StateSyncBuilder().withActivityRemoved(this).waitForAndVerify() - } } diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt index d3cee645cd99..0ee7aeeb30c6 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt @@ -74,24 +74,6 @@ constructor( open(expectedPackage) } - fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) { - val button = - uiDevice.wait( - Until.findObject(By.res(packageName, "start_dialog_themed_activity_btn")), - FIND_TIMEOUT - ) - - requireNotNull(button) { - "Button not found, this usually happens when the device " + - "was left in an unknown state (e.g. Screen turned off)" - } - button.click() - wmHelper - .StateSyncBuilder() - .withFullScreenApp(ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent()) - .waitForAndVerify() - } - fun dismissDialog(wmHelper: WindowManagerStateHelper) { val dialog = uiDevice.wait(Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT) @@ -126,20 +108,4 @@ constructor( } return false } - - fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) { - val button = - uiDevice.wait( - Until.findObject(By.res(packageName, "toggle_fixed_portrait_btn")), - FIND_TIMEOUT - ) - require(button != null) { - "Button not found, this usually happens when the device " + - "was left in an unknown state (e.g. Screen turned off)" - } - button.click() - instrumentation.waitForIdleSync() - // Ensure app relaunching transition finish and the IME has shown - waitIMEShown(wmHelper) - } } diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml index fa73e2c63780..507c1b622613 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml @@ -1,5 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- +<?xml version="1.0" encoding="utf-8"?><!-- Copyright 2018 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,39 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" + android:background="@android:color/holo_green_light" android:focusableInTouchMode="true" - android:background="@android:color/holo_green_light"> - <EditText android:id="@+id/plain_text_input" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:imeOptions="flagNoExtractUi" - android:inputType="text"/> - <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical"> + + <EditText + android:id="@+id/plain_text_input" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal"> - <Button - android:id="@+id/finish_activity_btn" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Finish activity" /> - <Button - android:id="@+id/start_dialog_themed_activity_btn" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Dialog themed activity" /> - <ToggleButton - android:id="@+id/toggle_fixed_portrait_btn" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textOn="Portrait (On)" - android:textOff="Portrait (Off)" - /> - </LinearLayout> + android:layout_height="wrap_content" + android:imeOptions="flagNoExtractUi" + android:inputType="text" /> </LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 8b334c0a8588..80c1dd072df7 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -40,6 +40,18 @@ public class ActivityOptions { public static final String LABEL = "ImeActivity"; public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ImeActivity"); + + /** Intent action used to finish the test activity. */ + public static final String ACTION_FINISH_ACTIVITY = + FLICKER_APP_PACKAGE + ".ImeActivity.FINISH_ACTIVITY"; + + /** Intent action used to start a {@link DialogThemedActivity}. */ + public static final String ACTION_START_DIALOG_THEMED_ACTIVITY = + FLICKER_APP_PACKAGE + ".ImeActivity.START_DIALOG_THEMED_ACTIVITY"; + + /** Intent action used to toggle activity orientation. */ + public static final String ACTION_TOGGLE_ORIENTATION = + FLICKER_APP_PACKAGE + ".ImeActivity.TOGGLE_ORIENTATION"; } public static class AutoFocusActivity { diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java index d7ee2af44111..4418b5a5ff82 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java @@ -16,12 +16,51 @@ package com.android.server.wm.flicker.testapp; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + +import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_FINISH_ACTIVITY; +import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_START_DIALOG_THEMED_ACTIVITY; +import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_TOGGLE_ORIENTATION; + import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Bundle; +import android.util.Log; import android.view.WindowManager; -import android.widget.Button; public class ImeActivity extends Activity { + + private static final String TAG = "ImeActivity"; + + /** Receiver used to handle actions coming from the test helper methods. */ + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case ACTION_FINISH_ACTIVITY -> finish(); + case ACTION_START_DIALOG_THEMED_ACTIVITY -> startActivity( + new Intent(context, DialogThemedActivity.class)); + case ACTION_TOGGLE_ORIENTATION -> { + mIsPortrait = !mIsPortrait; + setRequestedOrientation(mIsPortrait + ? SCREEN_ORIENTATION_PORTRAIT + : SCREEN_ORIENTATION_UNSPECIFIED); + } + default -> Log.w(TAG, "Unhandled action=" + intent.getAction()); + } + } + }; + + /** + * Used to toggle activity orientation between portrait when {@code true} and + * unspecified otherwise. + */ + private boolean mIsPortrait = false; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -30,9 +69,17 @@ public class ImeActivity extends Activity { .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; getWindow().setAttributes(p); setContentView(R.layout.activity_ime); - Button button = findViewById(R.id.finish_activity_btn); - button.setOnClickListener(view -> { - finish(); - }); + + final var filter = new IntentFilter(); + filter.addAction(ACTION_FINISH_ACTIVITY); + filter.addAction(ACTION_START_DIALOG_THEMED_ACTIVITY); + filter.addAction(ACTION_TOGGLE_ORIENTATION); + registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED); + } + + @Override + protected void onDestroy() { + unregisterReceiver(mBroadcastReceiver); + super.onDestroy(); } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java index 7ee8debddcf1..cd711f72d8fd 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java @@ -16,29 +16,12 @@ package com.android.server.wm.flicker.testapp; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; - -import android.content.Intent; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ToggleButton; - public class ImeActivityAutoFocus extends ImeActivity { @Override protected void onStart() { super.onStart(); - Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn); - startThemedActivityButton.setOnClickListener( - button -> startActivity(new Intent(this, DialogThemedActivity.class))); - - ToggleButton toggleFixedPortraitButton = findViewById(R.id.toggle_fixed_portrait_btn); - toggleFixedPortraitButton.setOnCheckedChangeListener( - (button, isChecked) -> setRequestedOrientation( - isChecked ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_UNSPECIFIED)); - - EditText editTextField = findViewById(R.id.plain_text_input); + final var editTextField = findViewById(R.id.plain_text_input); editTextField.requestFocus(); } } diff --git a/tests/InputScreenshotTest/Android.bp b/tests/InputScreenshotTest/Android.bp index 15aaa463cce7..83ced2c2258f 100644 --- a/tests/InputScreenshotTest/Android.bp +++ b/tests/InputScreenshotTest/Android.bp @@ -7,12 +7,27 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +filegroup { + name: "InputScreenshotTestRNGFiles", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + exclude_srcs: [ + "src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt", + "src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt", + ], +} + android_test { name: "InputScreenshotTests", srcs: [ "src/**/*.java", "src/**/*.kt", ], + exclude_srcs: [ + "src/android/input/screenshot/package-info.java", + ], platform_apis: true, certificate: "platform", static_libs: [ @@ -43,6 +58,7 @@ android_test { "hamcrest-library", "kotlin-test", "flag-junit", + "platform-parametric-runner-lib", "platform-test-annotations", "services.core.unboosted", "testables", diff --git a/tests/InputScreenshotTest/robotests/Android.bp b/tests/InputScreenshotTest/robotests/Android.bp new file mode 100644 index 000000000000..912f4b8069b4 --- /dev/null +++ b/tests/InputScreenshotTest/robotests/Android.bp @@ -0,0 +1,71 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_library { + name: "InputRoboRNGTestsAssetsLib", + asset_dirs: ["assets"], + sdk_version: "current", + platform_apis: true, + manifest: "AndroidManifest.xml", + optimize: { + enabled: false, + }, + use_resource_processor: true, +} + +android_app { + name: "InputRoboApp", + srcs: [], + static_libs: [ + "androidx.test.espresso.core", + "androidx.appcompat_appcompat", + "flag-junit", + "guava", + "InputRoboRNGTestsAssetsLib", + "platform-screenshot-diff-core", + "PlatformComposeSceneTransitionLayoutTestsUtils", + ], + manifest: "robo-manifest.xml", + aaptflags: [ + "--extra-packages", + "com.android.input.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: "InputRoboRNGTests", + srcs: [ + ":InputScreenshotTestRNGFiles", + ":flag-junit", + ":platform-test-screenshot-rules", + ], + // Do not add any new libraries here, they should be added to SystemUIGoogleRobo 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: "InputRoboApp", +} diff --git a/tests/InputScreenshotTest/robotests/AndroidManifest.xml b/tests/InputScreenshotTest/robotests/AndroidManifest.xml new file mode 100644 index 000000000000..56893113288d --- /dev/null +++ b/tests/InputScreenshotTest/robotests/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.input.screenshot"> + <uses-sdk android:minSdkVersion="21"/> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> +</manifest> diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png Binary files differnew file mode 100644 index 000000000000..baf204a6cfb3 --- /dev/null +++ b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png Binary files differnew file mode 100644 index 000000000000..deb3ceeca7fb --- /dev/null +++ b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png diff --git a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png Binary files differnew file mode 100644 index 000000000000..34e25f73d953 --- /dev/null +++ b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png diff --git a/tests/InputScreenshotTest/robotests/config/robolectric.properties b/tests/InputScreenshotTest/robotests/config/robolectric.properties new file mode 100644 index 000000000000..83d7549551ce --- /dev/null +++ b/tests/InputScreenshotTest/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/tests/InputScreenshotTest/robotests/robo-manifest.xml b/tests/InputScreenshotTest/robotests/robo-manifest.xml new file mode 100644 index 000000000000..e86f58ef0e55 --- /dev/null +++ b/tests/InputScreenshotTest/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.input.screenshot" + coreApp="true"> + <application> + <activity + android:name="androidx.activity.ComponentActivity" + android:exported="true"> + </activity> + </application> +</manifest> diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt index c2c3d5530a00..75dab41d3609 100644 --- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt +++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt @@ -18,6 +18,7 @@ package com.android.input.screenshot import android.content.Context import android.graphics.Bitmap +import android.os.Build import androidx.activity.ComponentActivity import androidx.compose.foundation.Image import androidx.compose.ui.platform.ViewRootForTest @@ -49,15 +50,17 @@ class InputScreenshotTestRule( ) ) 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) } /** @@ -84,4 +87,4 @@ class InputScreenshotTestRule( val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher) } -}
\ No newline at end of file +} diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt index 8ae6dfd8b63b..ab7bb4eda899 100644 --- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt +++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt @@ -26,14 +26,15 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain 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 /** A screenshot test for Keyboard layout preview for Iso physical layout. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } @@ -55,4 +56,4 @@ class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) } } -}
\ No newline at end of file +} diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java new file mode 100644 index 000000000000..4b5a56d3bd1d --- /dev/null +++ b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java @@ -0,0 +1,4 @@ +@GraphicsMode(GraphicsMode.Mode.NATIVE) +package com.android.input.screenshot; + +import org.robolectric.annotation.GraphicsMode; diff --git a/tests/SmokeTestApps/Android.bp b/tests/SmokeTestApps/Android.bp index 3505fe1c4afb..38ee8ac99747 100644 --- a/tests/SmokeTestApps/Android.bp +++ b/tests/SmokeTestApps/Android.bp @@ -11,4 +11,7 @@ android_test { name: "SmokeTestTriggerApps", srcs: ["src/**/*.java"], sdk_version: "current", + errorprone: { + enabled: false, + }, } diff --git a/tools/hoststubgen/hoststubgen/framework-policy-override.txt b/tools/hoststubgen/hoststubgen/framework-policy-override.txt index ff0fe32ad267..493ad56a5cbb 100644 --- a/tools/hoststubgen/hoststubgen/framework-policy-override.txt +++ b/tools/hoststubgen/hoststubgen/framework-policy-override.txt @@ -78,6 +78,9 @@ class android.util.Log !com.android.hoststubgen.nativesubstitution.Log_host class com.android.internal.util.FastPrintWriter keepclass class com.android.internal.util.LineBreakBufferedWriter keepclass +class android.util.EventLog stubclass +class android.util.EventLog !com.android.hoststubgen.nativesubstitution.EventLog_host +class android.util.EventLog$Event stubclass # Expose Context because it's referred to by AndroidTestCase, but don't need to expose any of # its members. diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java new file mode 100644 index 000000000000..292e8da0de10 --- /dev/null +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hoststubgen.nativesubstitution; + +import android.util.Log; +import android.util.Log.Level; + +import java.util.Collection; + +public class EventLog_host { + public static int writeEvent(int tag, int value) { + return writeEvent(tag, (Object) value); + } + + public static int writeEvent(int tag, long value) { + return writeEvent(tag, (Object) value); + } + + public static int writeEvent(int tag, float value) { + return writeEvent(tag, (Object) value); + } + + public static int writeEvent(int tag, String str) { + return writeEvent(tag, (Object) str); + } + + public static int writeEvent(int tag, Object... list) { + final StringBuilder sb = new StringBuilder(); + sb.append("logd: [event] "); + final String tagName = android.util.EventLog.getTagName(tag); + if (tagName != null) { + sb.append(tagName); + } else { + sb.append(tag); + } + sb.append(": ["); + for (int i = 0; i < list.length; i++) { + sb.append(String.valueOf(list[i])); + if (i < list.length - 1) { + sb.append(','); + } + } + sb.append(']'); + System.out.println(sb.toString()); + return sb.length(); + } + + public static void readEvents(int[] tags, Collection<android.util.EventLog.Event> output) { + throw new UnsupportedOperationException(); + } + + public static void readEventsOnWrapping(int[] tags, long timestamp, + Collection<android.util.EventLog.Event> output) { + throw new UnsupportedOperationException(); + } +} diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java index 4a3a79803b65..668c94c0f91c 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java @@ -27,9 +27,10 @@ import java.util.concurrent.atomic.AtomicLong; /** * Tentative, partial implementation of the Parcel native methods, using Java's - * {@link ByteBuffer}. It turned out there's enough semantics differences between Parcel - * and {@link ByteBuffer}, so it didn't actually work. - * (e.g. Parcel seems to allow moving the data position to be beyond its size? Which + * {@code byte[]}. + * (We don't use a {@link ByteBuffer} because there's enough semantics differences between Parcel + * and {@link ByteBuffer}, and it didn't work out. + * e.g. Parcel seems to allow moving the data position to be beyond its size? Which * {@link ByteBuffer} wouldn't allow...) */ public class Parcel_host { diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh index 89daa2084420..85038be80c51 100755 --- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh +++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# This command is expected to be executed with: atest hoststubgen-invoke-test + set -e # Exit when any command files # This script runs HostStubGen directly with various arguments and make sure @@ -35,6 +37,12 @@ if [[ "$TEMP" == "" ]] ; then mkdir -p $TEMP fi +cleanup_temp() { + rm -fr $TEMP/* +} + +cleanup_temp + JAR=hoststubgen-test-tiny-framework.jar STUB=$TEMP/stub.jar IMPL=$TEMP/impl.jar @@ -47,12 +55,10 @@ HOSTSTUBGEN_OUT=$TEMP/output.txt # HostStubGen result in it. HOSTSTUBGEN_RC=0 -# Define the functions to - - # Note, because the build rule will only install hoststubgen.jar, but not the wrapper script, # we need to execute it manually with the java command. hoststubgen() { + echo "Running hoststubgen with: $*" java -jar ./hoststubgen.jar "$@" } @@ -62,7 +68,7 @@ run_hoststubgen() { echo "# Test: $test_name" - rm -f $HOSTSTUBGEN_OUT + cleanup_temp local filter_arg="" @@ -73,11 +79,21 @@ run_hoststubgen() { cat $ANNOTATION_FILTER fi + local stub_arg="" + local impl_arg="" + + if [[ "$STUB" != "" ]] ; then + stub_arg="--out-stub-jar $STUB" + fi + if [[ "$IMPL" != "" ]] ; then + impl_arg="--out-impl-jar $IMPL" + fi + hoststubgen \ --debug \ --in-jar $JAR \ - --out-stub-jar $STUB \ - --out-impl-jar $IMPL \ + $stub_arg \ + $impl_arg \ --stub-annotation \ android.hosttest.annotation.HostSideTestStub \ --keep-annotation \ @@ -105,6 +121,21 @@ run_hoststubgen() { return 0 } +assert_file_generated() { + local file="$1" + if [[ "$file" == "" ]] ; then + if [[ -f "$file" ]] ; then + echo "HostStubGen shouldn't have generated $file" + return 1 + fi + else + if ! [[ -f "$file" ]] ; then + echo "HostStubGen didn't generate $file" + return 1 + fi + fi +} + run_hoststubgen_for_success() { run_hoststubgen "$@" @@ -112,6 +143,9 @@ run_hoststubgen_for_success() { echo "HostStubGen expected to finish successfully, but failed with $rc" return 1 fi + + assert_file_generated "$STUB" + assert_file_generated "$IMPL" } run_hoststubgen_for_failure() { @@ -189,6 +223,11 @@ run_hoststubgen_for_success "One specific class disallowed, but it doesn't use a * # All other classes allowed " +STUB="" run_hoststubgen_for_success "No stub generation" "" + +IMPL="" run_hoststubgen_for_success "No impl generation" "" + +STUB="" IMPL="" run_hoststubgen_for_success "No stub, no impl generation" "" echo "All tests passed" diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 07bd2dc7c867..3cdddc23b332 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -237,8 +237,8 @@ class HostStubGen(val options: HostStubGenOptions) { */ private fun convert( inJar: String, - outStubJar: String, - outImplJar: String, + outStubJar: String?, + outImplJar: String?, filter: OutputFilter, enableChecker: Boolean, classes: ClassNodes, @@ -254,8 +254,8 @@ class HostStubGen(val options: HostStubGenOptions) { log.withIndent { // Open the input jar file and process each entry. ZipFile(inJar).use { inZip -> - ZipOutputStream(FileOutputStream(outStubJar)).use { stubOutStream -> - ZipOutputStream(FileOutputStream(outImplJar)).use { implOutStream -> + maybeWithZipOutputStream(outStubJar) { stubOutStream -> + maybeWithZipOutputStream(outImplJar) { implOutStream -> val inEntries = inZip.entries() while (inEntries.hasMoreElements()) { val entry = inEntries.nextElement() @@ -265,22 +265,29 @@ class HostStubGen(val options: HostStubGenOptions) { log.i("Converted all entries.") } } - log.i("Created stub: $outStubJar") - log.i("Created impl: $outImplJar") + outStubJar?.let { log.i("Created stub: $it") } + outImplJar?.let { log.i("Created impl: $it") } } } val end = System.currentTimeMillis() log.v("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0) } + private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T { + if (filename == null) { + return block(null) + } + return ZipOutputStream(FileOutputStream(filename)).use(block) + } + /** * Convert a single ZIP entry, which may or may not be a class file. */ private fun convertSingleEntry( inZip: ZipFile, entry: ZipEntry, - stubOutStream: ZipOutputStream, - implOutStream: ZipOutputStream, + stubOutStream: ZipOutputStream?, + implOutStream: ZipOutputStream?, filter: OutputFilter, packageRedirector: PackageRedirectRemapper, enableChecker: Boolean, @@ -316,8 +323,8 @@ class HostStubGen(val options: HostStubGenOptions) { // Unknown type, we just copy it to both output zip files. // TODO: We probably shouldn't do it for stub jar? log.v("Copying: %s", entry.name) - copyZipEntry(inZip, entry, stubOutStream) - copyZipEntry(inZip, entry, implOutStream) + stubOutStream?.let { copyZipEntry(inZip, entry, it) } + implOutStream?.let { copyZipEntry(inZip, entry, it) } } } @@ -346,8 +353,8 @@ class HostStubGen(val options: HostStubGenOptions) { private fun processSingleClass( inZip: ZipFile, entry: ZipEntry, - stubOutStream: ZipOutputStream, - implOutStream: ZipOutputStream, + stubOutStream: ZipOutputStream?, + implOutStream: ZipOutputStream?, filter: OutputFilter, packageRedirector: PackageRedirectRemapper, enableChecker: Boolean, @@ -361,7 +368,7 @@ class HostStubGen(val options: HostStubGenOptions) { return } // Generate stub first. - if (classPolicy.policy.needsInStub) { + if (stubOutStream != null && classPolicy.policy.needsInStub) { log.v("Creating stub class: %s Policy: %s", classInternalName, classPolicy) log.withIndent { BufferedInputStream(inZip.getInputStream(entry)).use { bis -> @@ -374,8 +381,8 @@ class HostStubGen(val options: HostStubGenOptions) { } } } - log.v("Creating impl class: %s Policy: %s", classInternalName, classPolicy) - if (classPolicy.policy.needsInImpl) { + if (implOutStream != null && classPolicy.policy.needsInImpl) { + log.v("Creating impl class: %s Policy: %s", classInternalName, classPolicy) log.withIndent { BufferedInputStream(inZip.getInputStream(entry)).use { bis -> val newEntry = ZipEntry(entry.name) diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index da5348707528..83f873d38f1b 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -28,10 +28,10 @@ class HostStubGenOptions( var inJar: String = "", /** Output stub jar file */ - var outStubJar: String = "", + var outStubJar: String? = null, /** Output implementation jar file */ - var outImplJar: String = "", + var outImplJar: String? = null, var inputJarDumpFile: String? = null, @@ -71,7 +71,7 @@ class HostStubGenOptions( var enablePreTrace: Boolean = false, var enablePostTrace: Boolean = false, - var enableNonStubMethodCallDetection: Boolean = true, + var enableNonStubMethodCallDetection: Boolean = false, ) { companion object { @@ -209,11 +209,14 @@ class HostStubGenOptions( if (ret.inJar.isEmpty()) { throw ArgumentsException("Required option missing: --in-jar") } - if (ret.outStubJar.isEmpty()) { - throw ArgumentsException("Required option missing: --out-stub-jar") + if (ret.outStubJar == null && ret.outImplJar == null) { + log.w("Neither --out-stub-jar nor --out-impl-jar is set." + + " $COMMAND_NAME will not generate jar files.") } - if (ret.outImplJar.isEmpty()) { - throw ArgumentsException("Required option missing: --out-impl-jar") + + if (ret.enableNonStubMethodCallDetection) { + log.w("--enable-non-stub-method-check is not fully implemented yet." + + " See the todo in doesMethodNeedNonStubCallCheck().") } return ret diff --git a/tools/hoststubgen/hoststubgen/test-framework/README.md b/tools/hoststubgen/hoststubgen/test-framework/README.md index 20e2f873b152..f616ad61d219 100644 --- a/tools/hoststubgen/hoststubgen/test-framework/README.md +++ b/tools/hoststubgen/hoststubgen/test-framework/README.md @@ -14,12 +14,6 @@ tests. $ atest --no-bazel-mode HostStubGenTest-framework-test-host-test ``` -- With `run-ravenwood-test` - -``` -$ run-ravenwood-test HostStubGenTest-framework-test-host-test -``` - - Advanced option: `run-test-without-atest.sh` runs the test without using `atest` or `run-ravenwood-test` ``` diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md index f3c0450d42a3..3bfad9bd673b 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md @@ -13,12 +13,6 @@ This test doesn't use the actual android framework code. $ atest hoststubgen-test-tiny-test ``` -- With `run-ravenwood-test` should work too. This is the proper way to run it. - -``` -$ run-ravenwood-test hoststubgen-test-tiny-test -``` - - `run-test-manually.sh` also run the test, but it builds the stub/impl jars and the test without using the build system. This is useful for debugging the tool. diff --git a/tools/hoststubgen/scripts/Android.bp b/tools/hoststubgen/scripts/Android.bp index 5da805e5640e..b1ba07ec540d 100644 --- a/tools/hoststubgen/scripts/Android.bp +++ b/tools/hoststubgen/scripts/Android.bp @@ -18,9 +18,3 @@ genrule_defaults { tools: ["dump-jar"], cmd: "$(location dump-jar) -s -o $(out) $(in)", } - -sh_binary_host { - name: "run-ravenwood-test", - src: "run-ravenwood-test", - visibility: ["//visibility:public"], -} diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh index 2dac08969d44..82faa91e2cac 100755 --- a/tools/hoststubgen/scripts/run-all-tests.sh +++ b/tools/hoststubgen/scripts/run-all-tests.sh @@ -22,10 +22,10 @@ cd .. READY_TEST_MODULES=( HostStubGenTest-framework-all-test-host-test hoststubgen-test-tiny-test + CtsUtilTestCasesRavenwood ) MUST_BUILD_MODULES=( - run-ravenwood-test "${NOT_READY_TEST_MODULES[*]}" HostStubGenTest-framework-test ) @@ -51,8 +51,6 @@ run ./scripts/build-framework-hostside-jars-and-extract.sh # run ./scripts/build-framework-hostside-jars-without-genrules.sh # These tests should all pass. -run-ravenwood-test ${READY_TEST_MODULES[*]} - -run atest CtsUtilTestCasesRavenwood +run atest ${READY_TEST_MODULES[*]} echo ""${0##*/}" finished, with no unexpected failures. Ready to submit!"
\ No newline at end of file diff --git a/tools/hoststubgen/scripts/run-ravenwood-test b/tools/hoststubgen/scripts/run-ravenwood-test deleted file mode 100755 index 9bbb859f5c3d..000000000000 --- a/tools/hoststubgen/scripts/run-ravenwood-test +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/bash -# Copyright (C) 2023 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e - -# Script to run a "Ravenwood" host side test. -# -# A proper way to run these tests is to use `atest`, but `atest` has a known issue of loading -# unrelated jar files as the class path, so for now we use this script to run host side tests. - -# Copy (with some changes) some functions from ../common.sh, so this script can be used without it. - -m() { - if (( $SKIP_BUILD )) ; then - echo "Skipping build: $*" 1>&2 - return 0 - fi - run ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@" -} - -run() { - echo "Running: $*" 1>&2 - "$@" -} - -run_junit_test_jar() { - local jar="$1" - echo "Starting test: $jar ..." - run cd "${jar%/*}" - - run ${JAVA:-java} $JAVA_OPTS \ - -cp $jar \ - org.junit.runner.JUnitCore \ - com.android.hoststubgen.hosthelper.HostTestSuite || return 1 - return 0 -} - -help() { - cat <<'EOF' - - run-ravenwood-test -- Run Ravenwood host tests - - Usage: - run-ravenwood-test [options] MODULE-NAME ... - - Options: - -h: Help - -t: Run test only, without building - -b: Build only, without running - - Example: - run-ravenwood-test HostStubGenTest-framework-test-host-test - -EOF -} - -#------------------------------------------------------------------------- -# Parse options -#------------------------------------------------------------------------- -build=0 -test=0 - -while getopts "htb" opt; do - case "$opt" in - h) help; exit 0 ;; - t) - test=1 - ;; - b) - build=1 - ;; - esac -done -shift $(($OPTIND - 1)) - -# If neither -t nor -b is provided, then build and run./ -if (( ( $build + $test ) == 0 )) ; then - build=1 - test=1 -fi - - -modules=("${@}") - -if (( "${#modules[@]}" == 0 )); then - help - exit 1 -fi - -#------------------------------------------------------------------------- -# Build -#------------------------------------------------------------------------- -if (( $build )) ; then - run m "${modules[@]}" -fi - -#------------------------------------------------------------------------- -# Run -#------------------------------------------------------------------------- - -failures=0 -if (( test )) ; then - for module in "${modules[@]}"; do - if run_junit_test_jar "$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/${module}/${module}.jar"; then - : # passed. - else - failures=$(( failures + 1 )) - fi - done - - if (( $failures > 0 )) ; then - echo "$failures test jar(s) failed." 1>&2 - exit 2 - fi -fi - -exit 0
\ No newline at end of file |