diff options
822 files changed, 20618 insertions, 7122 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 16389b3bcc3c..b3e8ea8fabf7 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -26,6 +26,7 @@ aconfig_srcjars = [ ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", ":android.security.flags-aconfig-java{.generated_srcjars}", + ":android.service.chooser.flags-aconfig-java{.generated_srcjars}", ":android.service.notification.flags-aconfig-java{.generated_srcjars}", ":android.view.flags-aconfig-java{.generated_srcjars}", ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}", @@ -54,6 +55,7 @@ aconfig_srcjars = [ ":android.app.flags-aconfig-java{.generated_srcjars}", ":android.credentials.flags-aconfig-java{.generated_srcjars}", ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", + ":com.android.server.flags.pinner-aconfig-java{.generated_srcjars}", ":android.service.voice.flags-aconfig-java{.generated_srcjars}", ":android.media.tv.flags-aconfig-java{.generated_srcjars}", ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", @@ -583,6 +585,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Pinner Service +aconfig_declarations { + name: "com.android.server.flags.pinner-aconfig", + package: "com.android.server.flags", + srcs: ["services/core/java/com/android/server/flags/pinner.aconfig"], +} + +java_aconfig_library { + name: "com.android.server.flags.pinner-aconfig-java", + aconfig_declarations: "com.android.server.flags.pinner-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Voice aconfig_declarations { name: "android.service.voice.flags-aconfig", @@ -683,6 +698,19 @@ cc_aconfig_library { aconfig_declarations: "device_policy_aconfig_flags", } +// Chooser / "Sharesheet" +aconfig_declarations { + name: "android.service.chooser.flags-aconfig", + package: "android.service.chooser", + srcs: ["core/java/android/service/chooser/flags.aconfig"], +} + +java_aconfig_library { + name: "android.service.chooser.flags-aconfig-java", + aconfig_declarations: "android.service.chooser.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // JobScheduler aconfig_declarations { name: "framework-jobscheduler-job.flags-aconfig", @@ -755,6 +783,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.hardware.usb.flags-aconfig-java-host", + aconfig_declarations: "android.hardware.usb.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // WindowingTools aconfig_declarations { name: "android.tracing.flags-aconfig", diff --git a/Android.bp b/Android.bp index 78ffd6fb5e69..c1fb41f845e3 100644 --- a/Android.bp +++ b/Android.bp @@ -140,6 +140,9 @@ filegroup { // For the generated R.java and Manifest.java ":framework-res{.aapt.srcjar}", + // Java/AIDL sources to be moved out to CrashRecovery module + ":framework-crashrecovery-sources", + // etc. ":framework-javastream-protos", ":statslog-framework-java-gen", // FrameworkStatsLog.java diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java index 3a114173869a..d3b4f23477da 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java @@ -52,9 +52,8 @@ public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase private final TraceMarkParser mTraceMarkParser = new TraceMarkParser( "applyPostLayoutPolicy", "applySurfaceChanges", - "AppTransitionReady", - "closeSurfaceTransaction", - "openSurfaceTransaction", + "onTransactionReady", + "applyTransaction", "performLayout", "performSurfacePlacement", "prepareSurfaces", diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index a4174ee6ae17..79927020d5ea 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -1,17 +1,3 @@ -// b/303477132 -android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101] -android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101] -android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.AppSearchSession [101] -android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.AppSearchSession [101] -android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#isFeatureSupported" in android.app.appsearch.AppSearchSession [101] -android/app/appsearch/JoinSpec.java:219: lint: Unresolved link/see tag "android.app.appsearch.SearchSpec.RankingStrategy#RANKING_STRATEGY_JOIN_AGGREGATE_SCORE SearchSpec.RankingStrategy#RANKING_STRATEGY_JOIN_AGGREGATE_SCORE" in android.app.appsearch.JoinSpec.Builder [101] -android/app/appsearch/SearchSpec.java:230: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec [101] -android/app/appsearch/SearchSpec.java:237: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec [101] -android/app/appsearch/SearchSpec.java:244: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec [101] -android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101] -android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101] -android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101] - // b/303582215 android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101] android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101] diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 89776dba7878..820d2b0d607e 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -675,7 +675,11 @@ ui::Rotation BootAnimation::parseOrientationProperty() { ss << "ro.bootanim.set_orientation_" << displayId.value; return ss.str(); }(); - const auto syspropValue = android::base::GetProperty(syspropName, "ORIENTATION_0"); + auto syspropValue = android::base::GetProperty(syspropName, ""); + if (syspropValue == "") { + syspropValue = android::base::GetProperty("ro.bootanim.set_orientation_logical_0", ""); + } + if (syspropValue == "ORIENTATION_90") { return ui::ROTATION_90; } else if (syspropValue == "ORIENTATION_180") { diff --git a/cmds/uinput/Android.bp b/cmds/uinput/Android.bp index 4b08d968ac4a..da497dcf908e 100644 --- a/cmds/uinput/Android.bp +++ b/cmds/uinput/Android.bp @@ -22,7 +22,7 @@ java_binary { name: "uinput", wrapper: "uinput.sh", srcs: [ - "**/*.java", + "src/**/*.java", ":uinputcommand_aidl", ], required: ["libuinputcommand_jni"], diff --git a/cmds/uinput/TEST_MAPPING b/cmds/uinput/TEST_MAPPING new file mode 100644 index 000000000000..e7d619c73790 --- /dev/null +++ b/cmds/uinput/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "imports": [ + { + "path": "frameworks/native/services/inputflinger" + } + ], + "postsubmit": [ + { + "name": "UinputTests" + } + ] +} diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp index ec2b1f4db521..a78a46504684 100644 --- a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp +++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp @@ -96,9 +96,9 @@ JNIEnv* DeviceCallback::getJNIEnv() { return env; } -std::unique_ptr<UinputDevice> UinputDevice::open(int32_t id, const char* name, int32_t vid, - int32_t pid, uint16_t bus, uint32_t ffEffectsMax, - const char* port, +std::unique_ptr<UinputDevice> UinputDevice::open(int32_t id, const char* name, int32_t vendorId, + int32_t productId, int32_t versionId, uint16_t bus, + uint32_t ffEffectsMax, const char* port, std::unique_ptr<DeviceCallback> callback) { android::base::unique_fd fd(::open(UINPUT_PATH, O_RDWR | O_NONBLOCK | O_CLOEXEC)); if (!fd.ok()) { @@ -118,8 +118,9 @@ std::unique_ptr<UinputDevice> UinputDevice::open(int32_t id, const char* name, i strlcpy(setupDescriptor.name, name, UINPUT_MAX_NAME_SIZE); setupDescriptor.id.version = 1; setupDescriptor.id.bustype = bus; - setupDescriptor.id.vendor = vid; - setupDescriptor.id.product = pid; + setupDescriptor.id.vendor = vendorId; + setupDescriptor.id.product = productId; + setupDescriptor.id.version = versionId; setupDescriptor.ff_effects_max = ffEffectsMax; // Request device configuration. @@ -242,9 +243,9 @@ std::vector<int32_t> toVector(JNIEnv* env, jintArray javaArray) { return data; } -static jlong openUinputDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid, - jint pid, jint bus, jint ffEffectsMax, jstring rawPort, - jobject callback) { +static jlong openUinputDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, + jint vendorId, jint productId, jint versionId, jint bus, + jint ffEffectsMax, jstring rawPort, jobject callback) { ScopedUtfChars name(env, rawName); if (name.c_str() == nullptr) { return 0; @@ -255,8 +256,8 @@ static jlong openUinputDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, std::make_unique<uinput::DeviceCallback>(env, callback); std::unique_ptr<uinput::UinputDevice> d = - uinput::UinputDevice::open(id, name.c_str(), vid, pid, bus, ffEffectsMax, port.c_str(), - std::move(cb)); + uinput::UinputDevice::open(id, name.c_str(), vendorId, productId, versionId, bus, + ffEffectsMax, port.c_str(), std::move(cb)); return reinterpret_cast<jlong>(d.release()); } @@ -326,7 +327,7 @@ static jint getEvdevInputPropByLabel(JNIEnv* env, jclass /* clazz */, jstring ra static JNINativeMethod sMethods[] = { {"nativeOpenUinputDevice", - "(Ljava/lang/String;IIIIILjava/lang/String;" + "(Ljava/lang/String;IIIIIILjava/lang/String;" "Lcom/android/commands/uinput/Device$DeviceCallback;)J", reinterpret_cast<void*>(openUinputDevice)}, {"nativeInjectEvent", "(JIII)V", reinterpret_cast<void*>(injectEvent)}, diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.h b/cmds/uinput/jni/com_android_commands_uinput_Device.h index 6da3d7968ed0..9769a75bd9ef 100644 --- a/cmds/uinput/jni/com_android_commands_uinput_Device.h +++ b/cmds/uinput/jni/com_android_commands_uinput_Device.h @@ -46,9 +46,9 @@ private: class UinputDevice { public: - static std::unique_ptr<UinputDevice> open(int32_t id, const char* name, int32_t vid, - int32_t pid, uint16_t bus, uint32_t ff_effects_max, - const char* port, + static std::unique_ptr<UinputDevice> open(int32_t id, const char* name, int32_t vendorId, + int32_t productId, int32_t versionId, uint16_t bus, + uint32_t ff_effects_max, const char* port, std::unique_ptr<DeviceCallback> callback); virtual ~UinputDevice(); diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java index b0fa34c68092..787055c8cd89 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Device.java +++ b/cmds/uinput/src/com/android/commands/uinput/Device.java @@ -61,8 +61,9 @@ public class Device { System.loadLibrary("uinputcommand_jni"); } - private static native long nativeOpenUinputDevice(String name, int id, int vid, int pid, - int bus, int ffEffectsMax, String port, DeviceCallback callback); + private static native long nativeOpenUinputDevice(String name, int id, int vendorId, + int productId, int versionId, int bus, int ffEffectsMax, String port, + DeviceCallback callback); private static native void nativeCloseUinputDevice(long ptr); private static native void nativeInjectEvent(long ptr, int type, int code, int value); private static native void nativeConfigure(int handle, int code, int[] configs); @@ -71,7 +72,7 @@ public class Device { private static native int nativeGetEvdevEventCodeByLabel(int type, String label); private static native int nativeGetEvdevInputPropByLabel(String label); - public Device(int id, String name, int vid, int pid, int bus, + public Device(int id, String name, int vendorId, int productId, int versionId, int bus, SparseArray<int[]> configuration, int ffEffectsMax, SparseArray<InputAbsInfo> absInfo, String port) { mId = id; @@ -83,19 +84,20 @@ public class Device { mOutputStream = System.out; SomeArgs args = SomeArgs.obtain(); args.argi1 = id; - args.argi2 = vid; - args.argi3 = pid; - args.argi4 = bus; - args.argi5 = ffEffectsMax; + args.argi2 = vendorId; + args.argi3 = productId; + args.argi4 = versionId; + args.argi5 = bus; + args.argi6 = ffEffectsMax; if (name != null) { args.arg1 = name; } else { - args.arg1 = id + ":" + vid + ":" + pid; + args.arg1 = id + ":" + vendorId + ":" + productId; } if (port != null) { args.arg2 = port; } else { - args.arg2 = "uinput:" + id + ":" + vid + ":" + pid; + args.arg2 = "uinput:" + id + ":" + vendorId + ":" + productId; } mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget(); @@ -161,8 +163,10 @@ public class Device { case MSG_OPEN_UINPUT_DEVICE: SomeArgs args = (SomeArgs) msg.obj; String name = (String) args.arg1; - mPtr = nativeOpenUinputDevice(name, args.argi1, args.argi2, - args.argi3, args.argi4, args.argi5, (String) args.arg2, + mPtr = nativeOpenUinputDevice(name, args.argi1 /* id */, + args.argi2 /* vendorId */, args.argi3 /* productId */, + args.argi4 /* versionId */, args.argi5 /* bus */, + args.argi6 /* ffEffectsMax */, (String) args.arg2 /* port */, new DeviceCallback()); if (mPtr == 0) { RuntimeException ex = new RuntimeException( diff --git a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java index b89e2cdbd905..7652f2403f6e 100644 --- a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java +++ b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java @@ -19,8 +19,8 @@ package com.android.commands.uinput; import android.annotation.Nullable; import android.util.SparseArray; -import java.io.BufferedReader; import java.io.IOException; +import java.io.LineNumberReader; import java.io.Reader; import java.util.ArrayDeque; import java.util.ArrayList; @@ -47,10 +47,11 @@ public class EvemuParser implements EventParser { private static final int REGISTRATION_DELAY_MILLIS = 500; private static class CommentAwareReader { - private final BufferedReader mReader; + private final LineNumberReader mReader; + private String mPreviousLine; private String mNextLine; - CommentAwareReader(BufferedReader in) throws IOException { + CommentAwareReader(LineNumberReader in) throws IOException { mReader = in; mNextLine = findNextLine(); } @@ -90,12 +91,46 @@ public class EvemuParser implements EventParser { /** Moves to the next line of the file. */ public void advance() throws IOException { + mPreviousLine = mNextLine; mNextLine = findNextLine(); } public boolean isAtEndOfFile() { return mNextLine == null; } + + /** Returns the previous line, for error messages. */ + public String getPreviousLine() { + return mPreviousLine; + } + + /** Returns the number of the <b>previous</b> line. */ + public int getPreviousLineNumber() { + return mReader.getLineNumber() - 1; + } + } + + public static class ParsingException extends RuntimeException { + private final int mLineNumber; + private final String mLine; + + ParsingException(String message, CommentAwareReader reader) { + this(message, reader.getPreviousLine(), reader.getPreviousLineNumber()); + } + + ParsingException(String message, String line, int lineNumber) { + super(message); + mLineNumber = lineNumber; + mLine = line; + } + + /** Returns a nicely formatted error message, including the line number and line. */ + public String makeErrorMessage() { + return String.format(""" + Parsing error on line %d: %s + --> %s + """, mLineNumber, getMessage(), mLine); + } } private final CommentAwareReader mReader; @@ -107,7 +142,7 @@ public class EvemuParser implements EventParser { private final Queue<Event> mQueuedEvents = new ArrayDeque<>(2); public EvemuParser(Reader in) throws IOException { - mReader = new CommentAwareReader(new BufferedReader(in)); + mReader = new CommentAwareReader(new LineNumberReader(in)); mQueuedEvents.add(parseRegistrationEvent()); // The kernel takes a little time to set up an evdev device after the initial @@ -133,20 +168,22 @@ public class EvemuParser implements EventParser { return null; } - final String[] parts = expectLineWithParts("E", 4); + final String line = expectLine("E"); + final String[] parts = expectParts(line, 4); final String[] timeParts = parts[0].split("\\."); if (timeParts.length != 2) { - throw new RuntimeException("Invalid timestamp (does not contain a '.')"); + throw new ParsingException( + "Invalid timestamp '" + parts[0] + "' (should contain a single '.')", mReader); } // 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]); + parseLong(timeParts[0], 10) * 1_000_000 + parseInt(timeParts[1], 10); 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]); + final int eventType = parseInt(parts[1], 16); + final int eventCode = parseInt(parts[2], 16); + final int value = parseInt(parts[3], 10); eb.setInjections(new int[] {eventType, eventCode, value}); if (mLastEventTimeMicros == -1) { @@ -184,11 +221,12 @@ public class EvemuParser implements EventParser { 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 String idsLine = expectLine("I"); + final String[] idStrings = expectParts(idsLine, 4); + eb.setBusId(parseInt(idStrings[0], 16)); + eb.setVendorId(parseInt(idStrings[1], 16)); + eb.setProductId(parseInt(idStrings[2], 16)); + eb.setVersionId(parseInt(idStrings[3], 16)); final SparseArray<int[]> config = new SparseArray<>(); config.append(Event.UinputControlCode.UI_SET_PROPBIT.getValue(), parseProperties()); @@ -215,33 +253,39 @@ public class EvemuParser implements EventParser { } private int[] parseProperties() throws IOException { - final List<String> propBitmapParts = new ArrayList<>(); + final ArrayList<Integer> propBitmapParts = new ArrayList<>(); String line = acceptLine("P"); while (line != null) { - propBitmapParts.addAll(List.of(line.strip().split(" "))); + String[] parts = line.strip().split(" "); + propBitmapParts.ensureCapacity(propBitmapParts.size() + parts.length); + for (String part : parts) { + propBitmapParts.add(parseBitmapPart(part, line)); + } line = acceptLine("P"); } - return hexStringBitmapToEventCodes(propBitmapParts); + return bitmapToEventCodes(propBitmapParts); } private void parseAxisBitmaps(SparseArray<int[]> config) throws IOException { - final Map<Integer, List<String>> axisBitmapParts = new HashMap<>(); + final Map<Integer, ArrayList<Integer>> axisBitmapParts = new HashMap<>(); String line = acceptLine("B"); while (line != null) { final String[] parts = line.strip().split(" "); if (parts.length < 2) { - throw new RuntimeException( + throw new ParsingException( "Expected event type and at least one bitmap byte on 'B:' line; only found " - + parts.length + " elements"); + + parts.length + " elements", mReader); } - final int eventType = Integer.parseInt(parts[0], 16); + final int eventType = 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<>()); } + ArrayList<Integer> bitmapParts = axisBitmapParts.get(eventType); + bitmapParts.ensureCapacity(bitmapParts.size() + parts.length); for (int i = 1; i < parts.length; i++) { - axisBitmapParts.get(eventType).add(parts[i]); + axisBitmapParts.get(eventType).add(parseBitmapPart(parts[i], line)); } } line = acceptLine("B"); @@ -253,7 +297,7 @@ public class EvemuParser implements EventParser { } final Event.UinputControlCode controlCode = Event.UinputControlCode.forEventType(entry.getKey()); - final int[] eventCodes = hexStringBitmapToEventCodes(entry.getValue()); + final int[] eventCodes = bitmapToEventCodes(entry.getValue()); if (controlCode != null && eventCodes.length > 0) { config.append(controlCode.getValue(), eventCodes); eventTypesToSet.add(entry.getKey()); @@ -263,24 +307,33 @@ public class EvemuParser implements EventParser { Event.UinputControlCode.UI_SET_EVBIT.getValue(), unboxIntList(eventTypesToSet)); } + private int parseBitmapPart(String part, String line) { + int b = parseInt(part, 16); + if (b < 0x0 || b > 0xff) { + throw new ParsingException("Bitmap part '" + part + + "' invalid; parts must be hexadecimal values between 00 and ff.", mReader); + } + return b; + } + 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> " + throw new ParsingException( + "AbsInfo lines should have the format 'A: <index (hex)> <min> <max> <fuzz> " + "<flat> [<resolution>]'; expected 5 or 6 numbers but found " - + parts.length); + + parts.length, mReader); } - final int axisCode = Integer.parseInt(parts[0], 16); + final int axisCode = 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; + info.minimum = parseInt(parts[1], 10); + info.maximum = parseInt(parts[2], 10); + info.fuzz = parseInt(parts[3], 10); + info.flat = parseInt(parts[4], 10); + info.resolution = parts.length > 5 ? parseInt(parts[5], 10) : 0; absInfos.append(axisCode, info); line = acceptLine("A"); } @@ -305,7 +358,9 @@ public class EvemuParser implements EventParser { private String expectLine(String type) throws IOException { final String line = acceptLine(type); if (line == null) { - throw new RuntimeException("Expected line of type '" + type + "'"); + throw new ParsingException("Expected line of type '" + type + "'. (Lines should be in " + + "the order N, I, P, B, A, L, S, E.)", + mReader.peekLine(), mReader.getPreviousLineNumber() + 1); } else { return line; } @@ -325,9 +380,8 @@ public class EvemuParser implements EventParser { } 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 ': '"); + throw new ParsingException("Missing type separator ': '", + line, mReader.getPreviousLineNumber() + 1); } if (lineParts[0].equals(type)) { mReader.advance(); @@ -337,31 +391,37 @@ public class EvemuParser implements EventParser { } } - /** - * 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(" "); + private String[] expectParts(String line, int numParts) { + final String[] parts = line.strip().split(" "); if (parts.length != numParts) { - throw new RuntimeException("Expected a '" + type + "' line with " + numParts - + " parts, found one with " + parts.length); + throw new ParsingException( + "Expected a line with " + numParts + " space-separated parts, but found one " + + "with " + parts.length, mReader); } return parts; } - private static int[] hexStringBitmapToEventCodes(List<String> strs) { + private int parseInt(String s, int radix) { + try { + return Integer.parseInt(s, radix); + } catch (NumberFormatException ex) { + throw new ParsingException( + "'" + s + "' is not a valid integer of base " + radix, mReader); + } + } + + private long parseLong(String s, int radix) { + try { + return Long.parseLong(s, radix); + } catch (NumberFormatException ex) { + throw new ParsingException("'" + s + "' is not a valid long of base " + radix, mReader); + } + } + + private static int[] bitmapToEventCodes(List<Integer> bytes) { 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 iByte = 0; iByte < bytes.size(); iByte++) { + int b = bytes.get(iByte); for (int iBit = 0; iBit < 8; iBit++) { if ((b & 1) != 0) { codes.add(iByte * 8 + iBit); diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java index 5ec40e5d04e3..0f16a27aac1d 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Event.java +++ b/cmds/uinput/src/com/android/commands/uinput/Event.java @@ -94,8 +94,9 @@ public class Event { private int mId; private Command mCommand; private String mName; - private int mVid; - private int mPid; + private int mVendorId; + private int mProductId; + private int mVersionId; private int mBusId; private int[] mInjections; private SparseArray<int[]> mConfiguration; @@ -118,11 +119,15 @@ public class Event { } public int getVendorId() { - return mVid; + return mVendorId; } public int getProductId() { - return mPid; + return mProductId; + } + + public int getVersionId() { + return mVersionId; } public int getBus() { @@ -172,8 +177,8 @@ public class Event { return "Event{id=" + mId + ", command=" + mCommand + ", name=" + mName - + ", vid=" + mVid - + ", pid=" + mPid + + ", vid=" + mVendorId + + ", pid=" + mProductId + ", busId=" + mBusId + ", events=" + Arrays.toString(mInjections) + ", configuration=" + mConfiguration @@ -216,12 +221,16 @@ public class Event { mEvent.mConfiguration = configuration; } - public void setVid(int vid) { - mEvent.mVid = vid; + public void setVendorId(int vendorId) { + mEvent.mVendorId = vendorId; + } + + public void setProductId(int productId) { + mEvent.mProductId = productId; } - public void setPid(int pid) { - mEvent.mPid = pid; + public void setVersionId(int versionId) { + mEvent.mVersionId = versionId; } public void setBusId(int busId) { diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java index 888ec5a1d33a..ed3ff33f7e52 100644 --- a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java +++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java @@ -60,8 +60,8 @@ public class JsonStyleParser implements EventParser { case "command" -> eb.setCommand( Event.Command.valueOf(mReader.nextString().toUpperCase())); case "name" -> eb.setName(mReader.nextString()); - case "vid" -> eb.setVid(readInt()); - case "pid" -> eb.setPid(readInt()); + case "vid" -> eb.setVendorId(readInt()); + case "pid" -> eb.setProductId(readInt()); case "bus" -> eb.setBusId(readBus()); case "events" -> { int[] injections = readInjectedEvents().stream() diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java index 684a12fc8f37..04df27987d58 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java +++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java @@ -60,6 +60,10 @@ public class Uinput { stream = new FileInputStream(f); } (new Uinput(stream)).run(); + } catch (EvemuParser.ParsingException e) { + System.err.println(e.makeErrorMessage()); + error(e.makeErrorMessage(), e); + System.exit(1); } catch (Exception e) { error("Uinput injection failed.", e); System.exit(1); @@ -142,8 +146,9 @@ public class Uinput { "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!"); } int id = e.getId(); - Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), e.getBus(), - e.getConfiguration(), e.getFfEffectsMax(), e.getAbsInfo(), e.getPort()); + Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), + e.getVersionId(), e.getBus(), e.getConfiguration(), e.getFfEffectsMax(), + e.getAbsInfo(), e.getPort()); mDevices.append(id, d); } diff --git a/cmds/uinput/tests/Android.bp b/cmds/uinput/tests/Android.bp new file mode 100644 index 000000000000..e728bd270a46 --- /dev/null +++ b/cmds/uinput/tests/Android.bp @@ -0,0 +1,20 @@ +package { + default_applicable_licenses: ["frameworks_base_cmds_uinput_license"], +} + +android_test { + name: "UinputTests", + srcs: [ + "src/**/*.java", + ], + static_libs: [ + "androidx.test.runner", + "frameworks-base-testutils", + "platform-test-annotations", + "truth", + "uinput", + ], + test_suites: [ + "device-tests", + ], +} diff --git a/cmds/uinput/tests/AndroidManifest.xml b/cmds/uinput/tests/AndroidManifest.xml new file mode 100644 index 000000000000..c364c1c37c2e --- /dev/null +++ b/cmds/uinput/tests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?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.commands.uinput.tests"> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="Uinput Tests" + android:targetPackage="com.android.commands.uinput.tests" /> +</manifest> diff --git a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java new file mode 100644 index 000000000000..06b0aac271ad --- /dev/null +++ b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java @@ -0,0 +1,497 @@ +/* + * 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.tests; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.fail; + +import android.platform.test.annotations.Postsubmit; +import android.util.SparseArray; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.commands.uinput.EvemuParser; +import com.android.commands.uinput.Event; +import com.android.commands.uinput.Event.UinputControlCode; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.io.StringReader; + +import src.com.android.commands.uinput.InputAbsInfo; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Postsubmit +public class EvemuParserTest { + + private Event getRegistrationEvent(String fileContents) throws IOException { + StringReader reader = new StringReader(fileContents); + EvemuParser parser = new EvemuParser(reader); + Event event = parser.getNextEvent(); + assertThat(event.getCommand()).isEqualTo(Event.Command.REGISTER); + return event; + } + + @Test + public void testNameParsing() throws IOException { + Event event = getRegistrationEvent(""" + N: ACME Pointing Widget #4 + I: 0001 1234 5678 9abc + """); + assertThat(event.getName()).isEqualTo("ACME Pointing Widget #4"); + } + + @Test + public void testIdParsing() throws IOException { + Event event = getRegistrationEvent(""" + N: ACME Pointing Widget #4 + I: 0001 1234 5678 9abc + """); + assertThat(event.getBus()).isEqualTo(0x0001); + assertThat(event.getVendorId()).isEqualTo(0x1234); + assertThat(event.getProductId()).isEqualTo(0x5678); + assertThat(event.getVersionId()).isEqualTo(0x9abc); + } + + @Test + public void testPropertyBitmapParsing() throws IOException { + Event event = getRegistrationEvent(""" + N: ACME Pointing Widget #4 + I: 0001 1234 5678 9abc + P: 05 00 00 00 00 00 00 00 + P: 01 + """); + assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_PROPBIT.getValue())) + .asList().containsExactly(0, 2, 64); + } + + @Test + public void testEventBitmapParsing() throws IOException { + Event event = getRegistrationEvent(""" + N: ACME Pointing Widget #4 + I: 0001 1234 5678 9abc + B: 00 0b 00 00 00 00 00 00 00 # SYN + B: 01 00 00 03 00 00 00 00 00 # KEY + B: 01 00 01 00 00 00 00 00 00 + B: 02 03 00 00 00 00 00 00 00 # REL + B: 03 00 00 # ABS + """); + assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_EVBIT.getValue())) + .asList().containsExactly(Event.EV_KEY, Event.EV_REL); + assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_KEYBIT.getValue())) + .asList().containsExactly(16, 17, 72); + assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_RELBIT.getValue())) + .asList().containsExactly(0, 1); + assertThat(event.getConfiguration().contains(UinputControlCode.UI_SET_ABSBIT.getValue())) + .isFalse(); + } + + @Test + public void testEventBitmapParsing_WithForceFeedback() throws IOException { + Event event = getRegistrationEvent(""" + N: ACME Pointing Widget #4 + I: 0001 1234 5678 9abc + B: 15 05 # FF + """); + assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_EVBIT.getValue())) + .asList().containsExactly(Event.EV_FF); + assertThat(event.getConfiguration().get(UinputControlCode.UI_SET_FFBIT.getValue())) + .asList().containsExactly(0, 2); + assertThat(event.getFfEffectsMax()).isEqualTo(2); + } + + private void assertAbsInfo(InputAbsInfo info, int minimum, int maximum, int fuzz, int flat, + int resolution) { + assertThat(info).isNotNull(); + assertWithMessage("Incorrect minimum").that(info.minimum).isEqualTo(minimum); + assertWithMessage("Incorrect maximum").that(info.maximum).isEqualTo(maximum); + assertWithMessage("Incorrect fuzz").that(info.fuzz).isEqualTo(fuzz); + assertWithMessage("Incorrect flat").that(info.flat).isEqualTo(flat); + assertWithMessage("Incorrect resolution").that(info.resolution).isEqualTo(resolution); + } + + @Test + public void testAbsInfoParsing_WithResolution() throws IOException { + Event event = getRegistrationEvent(""" + N: ACME Weird Gamepad + I: 0001 1234 5678 9abc + A: 03 -128 128 4 4 0 # ABS_MT_RX + A: 2f 0 9 0 0 0 # ABS_MT_SLOT + A: 34 -4096 4096 0 0 0 # ABS_MT_ORIENTATION + A: 35 0 1599 0 0 11 # ABS_MT_POSITION_X + """); + SparseArray<InputAbsInfo> absInfos = event.getAbsInfo(); + assertThat(absInfos.size()).isEqualTo(4); + assertAbsInfo(absInfos.get(0x03), -128, 128, 4, 4, 0); + assertAbsInfo(absInfos.get(0x2f), 0, 9, 0, 0, 0); + assertAbsInfo(absInfos.get(0x34), -4096, 4096, 0, 0, 0); + assertAbsInfo(absInfos.get(0x35), 0, 1599, 0, 0, 11); + } + + @Test + public void testAbsInfoParsing_WithoutResolution() throws IOException { + Event event = getRegistrationEvent(""" + N: ACME Terrible Touchscreen + I: 0001 1234 5678 9abc + A: 2f 0 9 0 0 # ABS_MT_SLOT + A: 35 0 1599 0 0 # ABS_MT_POSITION_X + A: 36 0 2559 0 0 # ABS_MT_POSITION_X + """); + SparseArray<InputAbsInfo> absInfos = event.getAbsInfo(); + assertThat(absInfos.size()).isEqualTo(3); + assertAbsInfo(absInfos.get(0x2f), 0, 9, 0, 0, 0); + assertAbsInfo(absInfos.get(0x35), 0, 1599, 0, 0, 0); + assertAbsInfo(absInfos.get(0x36), 0, 2559, 0, 0, 0); + } + + @Test + public void testLedAndSwitchStatesIgnored() throws IOException { + // We don't support L: and S: lines yet, so all we need to check here is that they don't + // prevent the other events from being parsed. + StringReader reader = new StringReader(""" + N: ACME Widget + I: 0001 1234 5678 9abc + L: 00 0 + L: 09 1 + S: 0a 1 + E: 0.000001 0 0 0 # SYN_REPORT + """); + EvemuParser parser = new EvemuParser(reader); + assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER); + assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY); + assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.INJECT); + } + + private void assertInjectEvent(Event event, int eventType, int eventCode, int value) { + assertThat(event).isNotNull(); + assertThat(event.getCommand()).isEqualTo(Event.Command.INJECT); + assertThat(event.getInjections()).asList() + .containsExactly(eventType, eventCode, value).inOrder(); + } + + private void assertDelayEvent(Event event, int durationMillis) { + assertThat(event).isNotNull(); + assertThat(event.getCommand()).isEqualTo(Event.Command.DELAY); + assertThat(event.getDurationMillis()).isEqualTo(durationMillis); + } + + @Test + public void testEventParsing_OneFrame() throws IOException { + StringReader reader = new StringReader(""" + N: ACME Widget + I: 0001 1234 5678 9abc + E: 0.000001 0002 0000 0001 # REL_X +1 + E: 0.000001 0002 0001 -0002 # REL_Y -2 + E: 0.000001 0000 0000 0000 # SYN_REPORT + """); + EvemuParser parser = new EvemuParser(reader); + assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER); + assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY); + assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1); + assertInjectEvent(parser.getNextEvent(), 0x2, 0x1, -2); + assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0); + } + + @Test + public void testEventParsing_MultipleFrames() throws IOException { + StringReader reader = new StringReader(""" + N: ACME YesBird Typing Aid + I: 0001 1234 5678 9abc + E: 0.000001 0001 0015 0001 # KEY_Y press + E: 0.000001 0000 0000 0000 # SYN_REPORT + E: 0.010001 0001 0015 0000 # KEY_Y release + E: 0.010001 0000 0000 0000 # SYN_REPORT + E: 1.010001 0001 0015 0001 # KEY_Y press + E: 1.010001 0000 0000 0000 # SYN_REPORT + """); + EvemuParser parser = new EvemuParser(reader); + assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER); + assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY); + + assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1); + assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0); + + assertDelayEvent(parser.getNextEvent(), 10); + + assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0); + assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0); + + assertDelayEvent(parser.getNextEvent(), 1000); + + assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1); + assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0); + } + + @Test + public void testErrorLineNumberReporting() throws IOException { + StringReader reader = new StringReader(""" + # EVEMU 1.3 + N: ACME Widget + # Comment to make sure they're taken into account when numbering lines + I: 0001 1234 5678 9abc + 00 00 00 00 00 00 00 00 # Missing a type + E: 0.000001 0001 0015 0001 # KEY_Y press + E: 0.000001 0000 0000 0000 # SYN_REPORT + """); + try { + new EvemuParser(reader); + fail("Parser should have thrown an error about the line with the missing type."); + } catch (EvemuParser.ParsingException ex) { + assertThat(ex.makeErrorMessage()).startsWith("Parsing error on line 5:"); + } + } + + @Test + public void testFreeDesktopEvemuRecording() throws IOException { + // This is a real recording from FreeDesktop's evemu-record tool, as a basic compatibility + // check with the FreeDesktop tools. + // (CheckStyle objects to the long line here. It can be split up with escaped newlines once + // the fix for b/306423115 reaches Android.) + StringReader reader = new StringReader(""" + # EVEMU 1.3 + # Kernel: 6.5.6-1rodete4-amd64 + # DMI: dmi:bvnLENOVO:bvrXXXXXXXX(X.XX):bdXX/XX/XXXX:brX.XX:efrX.XX:svnLENOVO:pnXXXXXXXXXX:pvrThinkPadX1Carbon:rvnLENOVO:rnXXXXXXXXX:rvrXXXXX:cvnLENOVO:ctXX:cvrNone:skuLENOVO_MT_20KG_BU_Think_FM_ThinkPadX1Carbon: + # Input device name: "Synaptics TM3289-021" + # Input device ID: bus 0x1d vendor 0x6cb product 0000 version 0000 + # Size in mm: 96x52 + # Supported events: + # Event type 0 (EV_SYN) + # Event code 0 (SYN_REPORT) + # Event code 1 (SYN_CONFIG) + # Event code 2 (SYN_MT_REPORT) + # Event code 3 (SYN_DROPPED) + # Event code 4 ((null)) + # Event code 5 ((null)) + # Event code 6 ((null)) + # Event code 7 ((null)) + # Event code 8 ((null)) + # Event code 9 ((null)) + # Event code 10 ((null)) + # Event code 11 ((null)) + # Event code 12 ((null)) + # Event code 13 ((null)) + # Event code 14 ((null)) + # Event code 15 (SYN_MAX) + # Event type 1 (EV_KEY) + # Event code 272 (BTN_LEFT) + # Event code 325 (BTN_TOOL_FINGER) + # Event code 328 (BTN_TOOL_QUINTTAP) + # Event code 330 (BTN_TOUCH) + # Event code 333 (BTN_TOOL_DOUBLETAP) + # Event code 334 (BTN_TOOL_TRIPLETAP) + # Event code 335 (BTN_TOOL_QUADTAP) + # Event type 3 (EV_ABS) + # Event code 0 (ABS_X) + # Value 0 + # Min 0 + # Max 1936 + # Fuzz 0 + # Flat 0 + # Resolution 20 + # Event code 1 (ABS_Y) + # Value 0 + # Min 0 + # Max 1057 + # Fuzz 0 + # Flat 0 + # Resolution 20 + # Event code 24 (ABS_PRESSURE) + # Value 0 + # Min 0 + # Max 255 + # Fuzz 0 + # Flat 0 + # Resolution 0 + # Event code 47 (ABS_MT_SLOT) + # Value 0 + # Min 0 + # Max 4 + # Fuzz 0 + # Flat 0 + # Resolution 0 + # Event code 48 (ABS_MT_TOUCH_MAJOR) + # Value 0 + # Min 0 + # Max 15 + # Fuzz 0 + # Flat 0 + # Resolution 0 + # Event code 49 (ABS_MT_TOUCH_MINOR) + # Value 0 + # Min 0 + # Max 15 + # Fuzz 0 + # Flat 0 + # Resolution 0 + # Event code 52 (ABS_MT_ORIENTATION) + # Value 0 + # Min 0 + # Max 1 + # Fuzz 0 + # Flat 0 + # Resolution 0 + # Event code 53 (ABS_MT_POSITION_X) + # Value 0 + # Min 0 + # Max 1936 + # Fuzz 0 + # Flat 0 + # Resolution 20 + # Event code 54 (ABS_MT_POSITION_Y) + # Value 0 + # Min 0 + # Max 1057 + # Fuzz 0 + # Flat 0 + # Resolution 20 + # Event code 55 (ABS_MT_TOOL_TYPE) + # Value 0 + # Min 0 + # Max 15 + # Fuzz 0 + # Flat 0 + # Resolution 0 + # Event code 57 (ABS_MT_TRACKING_ID) + # Value 0 + # Min 0 + # Max 65535 + # Fuzz 0 + # Flat 0 + # Resolution 0 + # Event code 58 (ABS_MT_PRESSURE) + # Value 0 + # Min 0 + # Max 255 + # Fuzz 0 + # Flat 0 + # Resolution 0 + # Properties: + # Property type 0 (INPUT_PROP_POINTER) + # Property type 2 (INPUT_PROP_BUTTONPAD) + N: Synaptics TM3289-021 + I: 001d 06cb 0000 0000 + P: 05 00 00 00 00 00 00 00 + B: 00 0b 00 00 00 00 00 00 00 + B: 01 00 00 00 00 00 00 00 00 + B: 01 00 00 00 00 00 00 00 00 + B: 01 00 00 00 00 00 00 00 00 + B: 01 00 00 00 00 00 00 00 00 + B: 01 00 00 01 00 00 00 00 00 + B: 01 20 e5 00 00 00 00 00 00 + B: 01 00 00 00 00 00 00 00 00 + B: 01 00 00 00 00 00 00 00 00 + B: 01 00 00 00 00 00 00 00 00 + B: 01 00 00 00 00 00 00 00 00 + B: 01 00 00 00 00 00 00 00 00 + B: 01 00 00 00 00 00 00 00 00 + B: 02 00 00 00 00 00 00 00 00 + B: 03 03 00 00 01 00 80 f3 06 + B: 04 00 00 00 00 00 00 00 00 + B: 05 00 00 00 00 00 00 00 00 + B: 11 00 00 00 00 00 00 00 00 + B: 12 00 00 00 00 00 00 00 00 + B: 14 00 00 00 00 00 00 00 00 + B: 15 00 00 00 00 00 00 00 00 + B: 15 00 00 00 00 00 00 00 00 + A: 00 0 1936 0 0 20 + A: 01 0 1057 0 0 20 + A: 18 0 255 0 0 0 + A: 2f 0 4 0 0 0 + A: 30 0 15 0 0 0 + A: 31 0 15 0 0 0 + A: 34 0 1 0 0 0 + A: 35 0 1936 0 0 20 + A: 36 0 1057 0 0 20 + A: 37 0 15 0 0 0 + A: 39 0 65535 0 0 0 + A: 3a 0 255 0 0 0 + ################################ + # Waiting for events # + ################################ + E: 0.000001 0003 0039 0000\t# EV_ABS / ABS_MT_TRACKING_ID 0 + E: 0.000001 0003 0035 0891\t# EV_ABS / ABS_MT_POSITION_X 891 + E: 0.000001 0003 0036 0333\t# EV_ABS / ABS_MT_POSITION_Y 333 + E: 0.000001 0003 003a 0056\t# EV_ABS / ABS_MT_PRESSURE 56 + E: 0.000001 0003 0030 0001\t# EV_ABS / ABS_MT_TOUCH_MAJOR 1 + E: 0.000001 0003 0031 0001\t# EV_ABS / ABS_MT_TOUCH_MINOR 1 + E: 0.000001 0001 014a 0001\t# EV_KEY / BTN_TOUCH 1 + E: 0.000001 0001 0145 0001\t# EV_KEY / BTN_TOOL_FINGER 1 + E: 0.000001 0003 0000 0891\t# EV_ABS / ABS_X 891 + E: 0.000001 0003 0001 0333\t# EV_ABS / ABS_Y 333 + E: 0.000001 0003 0018 0056\t# EV_ABS / ABS_PRESSURE 56 + E: 0.000001 0000 0000 0000\t# ------------ SYN_REPORT (0) ---------- +0ms + E: 0.006081 0003 0035 0888\t# EV_ABS / ABS_MT_POSITION_X 888 + """); + EvemuParser parser = new EvemuParser(reader); + Event regEvent = parser.getNextEvent(); + assertThat(regEvent.getName()).isEqualTo("Synaptics TM3289-021"); + + assertThat(regEvent.getBus()).isEqualTo(0x001d); + assertThat(regEvent.getVendorId()).isEqualTo(0x6cb); + assertThat(regEvent.getProductId()).isEqualTo(0x0000); + // TODO(b/302297266): check version ID once it's supported + + assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_PROPBIT.getValue())) + .asList().containsExactly(0, 2); + + assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_EVBIT.getValue())) + .asList().containsExactly(Event.EV_KEY, Event.EV_ABS); + assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_KEYBIT.getValue())) + .asList().containsExactly(272, 325, 328, 330, 333, 334, 335); + assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_ABSBIT.getValue())) + .asList().containsExactly(0, 1, 24, 47, 48, 49, 52, 53, 54, 55, 57, 58); + + SparseArray<InputAbsInfo> absInfos = regEvent.getAbsInfo(); + assertAbsInfo(absInfos.get(0), 0, 1936, 0, 0, 20); + assertAbsInfo(absInfos.get(1), 0, 1057, 0, 0, 20); + assertAbsInfo(absInfos.get(24), 0, 255, 0, 0, 0); + assertAbsInfo(absInfos.get(47), 0, 4, 0, 0, 0); + assertAbsInfo(absInfos.get(48), 0, 15, 0, 0, 0); + assertAbsInfo(absInfos.get(49), 0, 15, 0, 0, 0); + assertAbsInfo(absInfos.get(52), 0, 1, 0, 0, 0); + assertAbsInfo(absInfos.get(53), 0, 1936, 0, 0, 20); + assertAbsInfo(absInfos.get(54), 0, 1057, 0, 0, 20); + assertAbsInfo(absInfos.get(55), 0, 15, 0, 0, 0); + assertAbsInfo(absInfos.get(57), 0, 65535, 0, 0, 0); + assertAbsInfo(absInfos.get(58), 0, 255, 0, 0, 0); + + assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY); + + assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0); + assertInjectEvent(parser.getNextEvent(), 0x3, 0x35, 891); + assertInjectEvent(parser.getNextEvent(), 0x3, 0x36, 333); + assertInjectEvent(parser.getNextEvent(), 0x3, 0x3a, 56); + assertInjectEvent(parser.getNextEvent(), 0x3, 0x30, 1); + assertInjectEvent(parser.getNextEvent(), 0x3, 0x31, 1); + assertInjectEvent(parser.getNextEvent(), 0x1, 0x14a, 1); + assertInjectEvent(parser.getNextEvent(), 0x1, 0x145, 1); + assertInjectEvent(parser.getNextEvent(), 0x3, 0x0, 891); + assertInjectEvent(parser.getNextEvent(), 0x3, 0x1, 333); + assertInjectEvent(parser.getNextEvent(), 0x3, 0x18, 56); + assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0); + + assertDelayEvent(parser.getNextEvent(), 6); + + assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888); + } +} diff --git a/core/api/current.txt b/core/api/current.txt index 812d4cd67ab7..9d13d8ad7905 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -16999,6 +16999,7 @@ package android.graphics { ctor public YuvImage(@NonNull byte[], int, int, int, @Nullable int[], @NonNull android.graphics.ColorSpace); method public boolean compressToJpeg(android.graphics.Rect, int, java.io.OutputStream); method public boolean compressToJpegR(@NonNull android.graphics.YuvImage, int, @NonNull java.io.OutputStream); + method @FlaggedApi("com.android.graphics.flags.yuv_image_compress_to_ultra_hdr") public boolean compressToJpegR(@NonNull android.graphics.YuvImage, int, @NonNull java.io.OutputStream, @NonNull byte[]); method @NonNull public android.graphics.ColorSpace getColorSpace(); method public int getHeight(); method public int[] getStrides(); @@ -40756,6 +40757,7 @@ package android.service.notification { public final class ZenPolicy implements android.os.Parcelable { method public int describeContents(); + method @FlaggedApi("android.app.modes_api") public int getAllowedChannels(); method public int getPriorityCallSenders(); method public int getPriorityCategoryAlarms(); method public int getPriorityCategoryCalls(); @@ -40776,6 +40778,9 @@ package android.service.notification { method public int getVisualEffectPeek(); method public int getVisualEffectStatusBar(); method public void writeToParcel(android.os.Parcel, int); + field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_NONE = 2; // 0x2 + field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_PRIORITY = 1; // 0x1 + field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_UNSET = 0; // 0x0 field public static final int CONVERSATION_SENDERS_ANYONE = 1; // 0x1 field public static final int CONVERSATION_SENDERS_IMPORTANT = 2; // 0x2 field public static final int CONVERSATION_SENDERS_NONE = 3; // 0x3 @@ -40796,6 +40801,7 @@ package android.service.notification { method @NonNull public android.service.notification.ZenPolicy.Builder allowAlarms(boolean); method @NonNull public android.service.notification.ZenPolicy.Builder allowAllSounds(); method @NonNull public android.service.notification.ZenPolicy.Builder allowCalls(int); + method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder allowChannels(int); method @NonNull public android.service.notification.ZenPolicy.Builder allowConversations(int); method @NonNull public android.service.notification.ZenPolicy.Builder allowEvents(boolean); method @NonNull public android.service.notification.ZenPolicy.Builder allowMedia(boolean); @@ -43736,7 +43742,9 @@ package android.telephony { field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array"; field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array"; field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_multiple_sa_proposals") public static final String KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL = "iwlan.supports_child_session_multiple_sa_proposals_bool"; field public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL = "iwlan.supports_eap_aka_fast_reauth_bool"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_multiple_sa_proposals") public static final String KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL = "iwlan.supports_ike_session_multiple_sa_proposals_bool"; } public abstract class CellIdentity implements android.os.Parcelable { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c282e4b6f3eb..ce5752fdbd8b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3867,7 +3867,7 @@ 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.read_install_info") @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, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; @@ -3902,7 +3902,7 @@ package android.content.pm { public static class PackageInstaller.InstallInfo { method public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException; - method public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams, @NonNull android.os.ParcelFileDescriptor) throws java.io.IOException; + method @FlaggedApi("android.content.pm.read_install_info") public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams, @NonNull android.os.ParcelFileDescriptor) throws java.io.IOException; method public int getInstallLocation(); method @NonNull public String getPackageName(); } @@ -4211,10 +4211,18 @@ package android.content.pm { public final class UserProperties implements android.os.Parcelable { method public int describeContents(); + method public int getShowInQuietMode(); + method public int getShowInSharingSurfaces(); method public boolean isCredentialShareableWithParent(); method public boolean isMediaSharedWithParent(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR; + field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2 + field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1 + field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0 + field public static final int SHOW_IN_SHARING_SURFACES_NO = 2; // 0x2 + field public static final int SHOW_IN_SHARING_SURFACES_SEPARATE = 1; // 0x1 + field public static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = 0; // 0x0 } } @@ -10560,6 +10568,7 @@ package android.os { method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getMainUser(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getPreviousForegroundUser(); + method @NonNull public String getProfileLabel(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 93932e4944ad..f4c8429619dd 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -373,6 +373,10 @@ package android.app { method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel); } + public static class NotificationManager.Policy implements android.os.Parcelable { + method @FlaggedApi("android.app.modes_api") public boolean allowPriorityChannels(); + } + public final class PendingIntent implements android.os.Parcelable { method public boolean addCancelListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.PendingIntent.CancelListener); method @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public boolean intentFilterEquals(@Nullable android.app.PendingIntent); @@ -776,6 +780,25 @@ package android.app.job { } +package android.app.pinner { + + @FlaggedApi("android.app.pinner_service_client_api") public final class PinnedFileStat implements android.os.Parcelable { + ctor @FlaggedApi("android.app.pinner_service_client_api") public PinnedFileStat(@NonNull String, long, @NonNull String); + method @FlaggedApi("android.app.pinner_service_client_api") public int describeContents(); + method @FlaggedApi("android.app.pinner_service_client_api") public long getBytesPinned(); + method @FlaggedApi("android.app.pinner_service_client_api") @NonNull public String getFilename(); + method @FlaggedApi("android.app.pinner_service_client_api") @NonNull public String getGroupName(); + method @FlaggedApi("android.app.pinner_service_client_api") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("android.app.pinner_service_client_api") @NonNull public static final android.os.Parcelable.Creator<android.app.pinner.PinnedFileStat> CREATOR; + } + + @FlaggedApi("android.app.pinner_service_client_api") public class PinnerServiceClient { + ctor @FlaggedApi("android.app.pinner_service_client_api") public PinnerServiceClient(); + method @FlaggedApi("android.app.pinner_service_client_api") @NonNull public java.util.List<android.app.pinner.PinnedFileStat> getPinnerStats(); + } + +} + package android.app.prediction { public final class AppPredictor { @@ -4197,8 +4220,13 @@ package android.window { public static class WindowInfosListenerForTest.WindowInfo { field @NonNull public final android.graphics.Rect bounds; field public final int displayId; + field public final boolean isDuplicateTouchToWallpaper; + field public final boolean isFocusable; + field public final boolean isPreventSplitting; + field public final boolean isTouchable; field public final boolean isTrustedOverlay; field public final boolean isVisible; + field public final boolean isWatchOutsideTouch; field @NonNull public final String name; field @NonNull public final android.graphics.Matrix transform; field @NonNull public final android.os.IBinder windowToken; diff --git a/core/java/Android.bp b/core/java/Android.bp index dfe3344a466a..fb1e16a27d0b 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -546,6 +546,15 @@ filegroup { ], } +// PackageManager common +filegroup { + name: "framework-pm-common-shared-srcs", + srcs: [ + "com/android/server/pm/pkg/AndroidPackage.java", + "com/android/server/pm/pkg/AndroidPackageSplit.java", + ], +} + java_library { name: "protolog-lib", platform_apis: true, diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java index c376eaeeac36..76735b647389 100644 --- a/core/java/android/accounts/Account.java +++ b/core/java/android/accounts/Account.java @@ -38,6 +38,7 @@ import java.util.Set; * {@link Parcelable} and also overrides {@link #equals} and {@link #hashCode}, making it * suitable for use as the key of a {@link java.util.Map} */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Account implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static final String TAG = "Account"; @@ -104,18 +105,27 @@ public class Account implements Parcelable { if (accessId != null) { synchronized (sAccessedAccounts) { if (sAccessedAccounts.add(this)) { - try { - IAccountManager accountManager = IAccountManager.Stub.asInterface( - ServiceManager.getService(Context.ACCOUNT_SERVICE)); - accountManager.onAccountAccessed(accessId); - } catch (RemoteException e) { - Log.e(TAG, "Error noting account access", e); - } + onAccountAccessed(accessId); } } } } + @android.ravenwood.annotation.RavenwoodReplace + private static void onAccountAccessed(String accessId) { + try { + IAccountManager accountManager = IAccountManager.Stub.asInterface( + ServiceManager.getService(Context.ACCOUNT_SERVICE)); + accountManager.onAccountAccessed(accessId); + } catch (RemoteException e) { + Log.e(TAG, "Error noting account access", e); + } + } + + private static void onAccountAccessed$ravenwood(String accessId) { + // No AccountManager to communicate with; ignored + } + /** @hide */ public String getAccessId() { return accessId; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 6c10f495e7bf..00432dcf152c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -24,9 +24,7 @@ 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; - import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; - import static java.lang.Character.MIN_VALUE; import android.annotation.AnimRes; @@ -7604,17 +7602,15 @@ public class Activity extends ContextThemeWrapper * @param taskDescription The TaskDescription properties that describe the task with this activity */ public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { - 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)); + 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)); + } } ActivityClient.getInstance().setTaskDescription(mToken, mTaskDescription); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index a4c3bb824502..87c86df6140d 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2934,6 +2934,15 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public String getSuspendingPackage(String suspendedPackage) { + try { + return mPM.getSuspendingPackage(suspendedPackage, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override public boolean isPackageSuspendedForUser(String packageName, int userId) { try { return mPM.isPackageSuspendedForUser(packageName, userId); diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index df6badcffe8e..d54074818b41 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -328,7 +328,7 @@ interface IActivityTaskManager { * A splash screen view has copied. */ void onSplashScreenViewCopyFinished(int taskId, - in SplashScreenView.SplashScreenViewParcelable material); + in @nullable SplashScreenView.SplashScreenViewParcelable material); /** * When the Picture-in-picture state has changed. diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index 369a78144fd3..b2be27f70ccc 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -201,6 +201,7 @@ public class LocaleConfig implements Parcelable { String defaultLocale = null; if (android.content.res.Flags.defaultLocale()) { + // Read the defaultLocale attribute of the LocaleConfig element TypedArray att = res.obtainAttributes( attrs, com.android.internal.R.styleable.LocaleConfig); defaultLocale = att.getString( diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 6c9c14fbc83a..d23b16d636a7 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -2058,6 +2058,14 @@ public class NotificationManager { public static final int STATE_CHANNELS_BYPASSING_DND = 1 << 0; /** + * Whether the policy indicates that even priority channels are NOT permitted to bypass DND. + * Note that this state explicitly marks the "disallow" state because the default behavior + * is to allow priority channels to break through. + * @hide + */ + public static final int STATE_PRIORITY_CHANNELS_BLOCKED = 1 << 1; + + /** * @hide */ public static final int STATE_UNSET = -1; @@ -2272,20 +2280,34 @@ public class NotificationManager { @Override public String toString() { - return "NotificationManager.Policy[" - + "priorityCategories=" + priorityCategoriesToString(priorityCategories) - + ",priorityCallSenders=" + prioritySendersToString(priorityCallSenders) - + ",priorityMessageSenders=" + prioritySendersToString(priorityMessageSenders) - + ",priorityConvSenders=" - + conversationSendersToString(priorityConversationSenders) - + ",suppressedVisualEffects=" - + suppressedEffectsToString(suppressedVisualEffects) - + ",areChannelsBypassingDnd=" + (state == STATE_UNSET - ? "unset" - : ((state & STATE_CHANNELS_BYPASSING_DND) != 0) - ? "true" - : "false") - + "]"; + StringBuilder sb = new StringBuilder().append("NotificationManager.Policy[") + .append("priorityCategories=") + .append(priorityCategoriesToString(priorityCategories)) + .append(",priorityCallSenders=") + .append(prioritySendersToString(priorityCallSenders)) + .append(",priorityMessageSenders=") + .append(prioritySendersToString(priorityMessageSenders)) + .append(",priorityConvSenders=") + .append(conversationSendersToString(priorityConversationSenders)) + .append(",suppressedVisualEffects=") + .append(suppressedEffectsToString(suppressedVisualEffects)); + if (Flags.modesApi()) { + sb.append(",hasPriorityChannels="); + } else { + sb.append(",areChannelsBypassingDnd="); + } + sb.append((state == STATE_UNSET + ? "unset" + : ((state & STATE_CHANNELS_BYPASSING_DND) != 0) + ? "true" + : "false")); + if (Flags.modesApi()) { + sb.append(",allowPriorityChannels=") + .append((state == STATE_UNSET + ? "unset" + : (allowPriorityChannels() ? "true" : "false"))); + } + return sb.append("]").toString(); } /** @hide */ @@ -2556,6 +2578,35 @@ public class NotificationManager { return (suppressedVisualEffects & SUPPRESSED_EFFECT_NOTIFICATION_LIST) == 0; } + /** @hide **/ + @FlaggedApi(Flags.FLAG_MODES_API) + @TestApi // so CTS tests can read this state without having to use implementation detail + public boolean allowPriorityChannels() { + if (state == STATE_UNSET) { + return true; // default + } + return (state & STATE_PRIORITY_CHANNELS_BLOCKED) == 0; + } + + /** @hide */ + @FlaggedApi(Flags.FLAG_MODES_API) + public boolean hasPriorityChannels() { + return (state & STATE_CHANNELS_BYPASSING_DND) != 0; + } + + /** @hide **/ + @FlaggedApi(Flags.FLAG_MODES_API) + public static int policyState(boolean hasPriorityChannels, boolean allowPriorityChannels) { + int state = 0; + if (hasPriorityChannels) { + state |= STATE_CHANNELS_BYPASSING_DND; + } + if (!allowPriorityChannels) { + state |= STATE_PRIORITY_CHANNELS_BLOCKED; + } + return state; + } + /** * returns a deep copy of this policy * @hide diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index d8448dcb4f9c..772b0b46d3b4 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -85,6 +85,9 @@ per-file IEphemeralResolver.aidl = file:/services/core/java/com/android/server/p per-file IInstantAppResolver.aidl = file:/services/core/java/com/android/server/pm/OWNERS per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/server/pm/OWNERS +# Pinner +per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS + # ResourcesManager per-file ResourcesManager.java = file:RESOURCES_OWNERS diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java index 88161091b6ea..145efa961bcb 100644 --- a/core/java/android/app/RemoteInput.java +++ b/core/java/android/app/RemoteInput.java @@ -429,7 +429,7 @@ public final class RemoteInput implements Parcelable { if (clipDataIntent == null) { return null; } - return clipDataIntent.getExtras().getParcelable(EXTRA_RESULTS_DATA, android.os.Bundle.class); + return clipDataIntent.getParcelableExtra(EXTRA_RESULTS_DATA, android.os.Bundle.class); } /** diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index bf5bad3dc0cf..fb0edb954539 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -22,3 +22,17 @@ flag { is_fixed_read_only: true } +flag { + name: "lifetime_extension_refactor" + namespace: "systemui" + description: "Enables moving notification lifetime extension management from SystemUI to " + "Notification Manager Service" + bug: "299448097" +} + +flag { + name: "visit_risky_uris" + namespace: "systemui" + description: "Guards the security fix that ensures all URIs in intents and Person.java are valid" + bug: "281044385" +} diff --git a/core/java/android/app/pinner-client.aconfig b/core/java/android/app/pinner-client.aconfig new file mode 100644 index 000000000000..b60ad9ee1f8d --- /dev/null +++ b/core/java/android/app/pinner-client.aconfig @@ -0,0 +1,8 @@ +package: "android.app" + +flag { + namespace: "system_performance" + name: "pinner_service_client_api" + description: "Control exposing PinnerService APIs." + bug: "307594624" +}
\ No newline at end of file diff --git a/core/java/android/app/pinner/IPinnerService.aidl b/core/java/android/app/pinner/IPinnerService.aidl new file mode 100644 index 000000000000..e5d0a05dd259 --- /dev/null +++ b/core/java/android/app/pinner/IPinnerService.aidl @@ -0,0 +1,12 @@ +package android.app.pinner; + +import android.app.pinner.PinnedFileStat; + +/** + * Interface for processes to communicate with system's PinnerService. + * @hide + */ +interface IPinnerService { + @EnforcePermission("DUMP") + List<PinnedFileStat> getPinnerStats(); +}
\ No newline at end of file diff --git a/core/java/android/app/pinner/OWNERS b/core/java/android/app/pinner/OWNERS new file mode 100644 index 000000000000..3e3fa66ca916 --- /dev/null +++ b/core/java/android/app/pinner/OWNERS @@ -0,0 +1,10 @@ +carmenjackson@google.com +dualli@google.com +edgararriaga@google.com +kevinjeon@google.com +philipcuadra@google.com +shombert@google.com +timmurray@google.com +wessam@google.com +jdduke@google.com +shayba@google.com
\ No newline at end of file diff --git a/core/java/android/app/pinner/PinnedFileStat.aidl b/core/java/android/app/pinner/PinnedFileStat.aidl new file mode 100644 index 000000000000..44217cf57d4d --- /dev/null +++ b/core/java/android/app/pinner/PinnedFileStat.aidl @@ -0,0 +1,3 @@ +package android.app.pinner; + +parcelable PinnedFileStat;
\ No newline at end of file diff --git a/core/java/android/app/pinner/PinnedFileStat.java b/core/java/android/app/pinner/PinnedFileStat.java new file mode 100644 index 000000000000..2e36330fc6df --- /dev/null +++ b/core/java/android/app/pinner/PinnedFileStat.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.pinner; + +import static android.app.Flags.FLAG_PINNER_SERVICE_CLIENT_API; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +@TestApi +@FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) +public final class PinnedFileStat implements Parcelable { + private String filename; + private long bytesPinned; + private String groupName; + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public long getBytesPinned() { + return bytesPinned; + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public @NonNull String getFilename() { + return filename; + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public @NonNull String getGroupName() { + return groupName; + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public PinnedFileStat(@NonNull String filename, long bytesPinned, @NonNull String groupName) { + this.filename = filename; + this.bytesPinned = bytesPinned; + this.groupName = groupName; + } + + private PinnedFileStat(Parcel source) { + readFromParcel(source); + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(filename); + dest.writeLong(bytesPinned); + dest.writeString8(groupName); + } + + private void readFromParcel(@NonNull Parcel source) { + filename = source.readString8(); + bytesPinned = source.readLong(); + groupName = source.readString8(); + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + @Override + public int describeContents() { + return 0; + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public static final @NonNull Creator<PinnedFileStat> CREATOR = new Creator<>() { + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + @Override + public PinnedFileStat createFromParcel(Parcel source) { + return new PinnedFileStat(source); + } + + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + @Override + public PinnedFileStat[] newArray(int size) { + return new PinnedFileStat[size]; + } + }; +} diff --git a/core/java/android/app/pinner/PinnerServiceClient.java b/core/java/android/app/pinner/PinnerServiceClient.java new file mode 100644 index 000000000000..8b7c6ccb51f2 --- /dev/null +++ b/core/java/android/app/pinner/PinnerServiceClient.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.pinner; + +import static android.app.Flags.FLAG_PINNER_SERVICE_CLIENT_API; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.app.pinner.IPinnerService; +import android.app.pinner.PinnedFileStat; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.List; + +/** + * Expose PinnerService as an interface to apps. + * @hide + */ +@TestApi +@FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) +public class PinnerServiceClient { + private static String TAG = "PinnerServiceClient"; + /** + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public PinnerServiceClient() {} + + /** + * Obtain the pinned file stats used for testing infrastructure. + * @return List of pinned files or an empty list if failed to retrieve them. + * @throws RuntimeException on failure to retrieve stats. + * @hide + */ + @TestApi + @FlaggedApi(FLAG_PINNER_SERVICE_CLIENT_API) + public @NonNull List<PinnedFileStat> getPinnerStats() { + IBinder binder = ServiceManager.getService("pinner"); + if (binder == null) { + Slog.w(TAG, + "Failed to retrieve PinnerService. A common failure reason is due to a lack of selinux permissions."); + return new ArrayList<>(); + } + IPinnerService pinnerService = IPinnerService.Stub.asInterface(binder); + if (pinnerService == null) { + Slog.w(TAG, "Failed to cast PinnerService."); + return new ArrayList<>(); + } + List<PinnedFileStat> stats; + try { + stats = pinnerService.getPinnerStats(); + } catch (RemoteException e) { + throw new RuntimeException("Failed to retrieve stats from PinnerService"); + } + return stats; + } +} diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING index 47a152aa6cca..0f7a0700b370 100644 --- a/core/java/android/app/time/TEST_MAPPING +++ b/core/java/android/app/time/TEST_MAPPING @@ -7,12 +7,20 @@ "include-filter": "android.app." } ] + }, + { + "name": "CtsTimeTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] } ], // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. "postsubmit": [ { - "name": "FrameworksServicesTests", + "name": "FrameworksTimeServicesTests", "options": [ { "include-filter": "com.android.server.timezonedetector." @@ -21,14 +29,6 @@ "include-filter": "com.android.server.timedetector." } ] - }, - { - "name": "CtsTimeTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] } ] } diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING index 9517fb99b04a..43dd82f8a26e 100644 --- a/core/java/android/app/timedetector/TEST_MAPPING +++ b/core/java/android/app/timedetector/TEST_MAPPING @@ -7,23 +7,23 @@ "include-filter": "android.app." } ] - } - ], - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + }, { - "name": "FrameworksServicesTests", + "name": "CtsTimeTestCases", "options": [ { - "include-filter": "com.android.server.timedetector." + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] - }, + } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ { - "name": "CtsTimeTestCases", + "name": "FrameworksTimeServicesTests", "options": [ { - "exclude-annotation": "androidx.test.filters.FlakyTest" + "include-filter": "com.android.server.timedetector." } ] } diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING index fd41b869efaf..2be5614b54a5 100644 --- a/core/java/android/app/timezonedetector/TEST_MAPPING +++ b/core/java/android/app/timezonedetector/TEST_MAPPING @@ -7,23 +7,23 @@ "include-filter": "android.app." } ] - } - ], - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + }, { - "name": "FrameworksServicesTests", + "name": "CtsTimeTestCases", "options": [ { - "include-filter": "com.android.server.timezonedetector." + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] - }, + } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ { - "name": "CtsTimeTestCases", + "name": "FrameworksTimeServicesTests", "options": [ { - "exclude-annotation": "androidx.test.filters.FlakyTest" + "include-filter": "com.android.server.timezonedetector." } ] } diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index 10da8b1c8203..f0477d47f723 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -32,6 +32,13 @@ flag { } flag { + name: "consistent_display_flags" + namespace: "virtual_devices" + description: "Make virtual display flags consistent with display manager" + bug: "300905478" +} + +flag { name: "vdm_custom_home" namespace: "virtual_devices" description: "Enable custom home API" diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index babfba13afcd..98623de810c4 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -312,6 +312,8 @@ interface IPackageManager { Bundle getSuspendedPackageAppExtras(String packageName, int userId); + String getSuspendingPackage(String packageName, int userId); + /** * Backup/restore support - only the system uid may use these. */ diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 4f0bfc7437c7..6df1f600c3ef 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2337,6 +2337,7 @@ public class PackageInstaller { */ @SystemApi @NonNull + @FlaggedApi(Flags.FLAG_READ_INSTALL_INFO) public InstallInfo readInstallInfo(@NonNull ParcelFileDescriptor pfd, @Nullable String debugPathName, int flags) throws PackageParsingException { final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); @@ -2516,6 +2517,7 @@ public class PackageInstaller { * and all relevant native code. * @throws IOException when size of native binaries cannot be calculated. */ + @FlaggedApi(Flags.FLAG_READ_INSTALL_INFO) public long calculateInstalledSize(@NonNull SessionParams params, @NonNull ParcelFileDescriptor pfd) throws IOException { return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride, diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index fe31c9dbf27d..6775f9b8d84d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -9965,6 +9965,21 @@ public abstract class PackageManager { } /** + * Get the name of the package that suspended the given package. Packages can be suspended by + * device administrators or apps holding {@link android.Manifest.permission#MANAGE_USERS} or + * {@link android.Manifest.permission#SUSPEND_APPS}. + * + * @param suspendedPackage The package that has been suspended. + * @return Name of the package that suspended the given package. Returns {@code null} if the + * given package is not currently suspended and the platform package name - i.e. + * {@code "android"} - if the package was suspended by a device admin. + * @hide + */ + public @Nullable String getSuspendingPackage(@NonNull String suspendedPackage) { + throw new UnsupportedOperationException("getSuspendingPackage not implemented"); + } + + /** * Query if an app is currently stopped. * * @return {@code true} if the given package is stopped, {@code false} otherwise diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index f532c4cffcc4..445ca0c98416 100644 --- a/core/java/android/content/pm/UserProperties.java +++ b/core/java/android/content/pm/UserProperties.java @@ -19,6 +19,7 @@ package android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.Parcel; @@ -49,7 +50,8 @@ public final class UserProperties implements Parcelable { private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher"; private static final String ATTR_START_WITH_PARENT = "startWithParent"; private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings"; - private static final String ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE = "hideInSettingsInQuietMode"; + private static final String ATTR_SHOW_IN_QUIET_MODE = "showInQuietMode"; + private static final String ATTR_SHOW_IN_SHARING_SURFACES = "showInSharingSurfaces"; private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy"; private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts"; private static final String ATTR_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA = @@ -81,7 +83,8 @@ public final class UserProperties implements Parcelable { INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT, INDEX_DELETE_APP_WITH_PARENT, INDEX_ALWAYS_VISIBLE, - INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE, + INDEX_SHOW_IN_QUIET_MODE, + INDEX_SHOW_IN_SHARING_SURFACES, INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE, }) @Retention(RetentionPolicy.SOURCE) @@ -99,8 +102,9 @@ public final class UserProperties implements Parcelable { private static final int INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT = 9; private static final int INDEX_DELETE_APP_WITH_PARENT = 10; private static final int INDEX_ALWAYS_VISIBLE = 11; - private static final int INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE = 12; + private static final int INDEX_SHOW_IN_QUIET_MODE = 12; private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13; + private static final int INDEX_SHOW_IN_SHARING_SURFACES = 14; /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */ private long mPropertiesPresent = 0; @@ -286,6 +290,81 @@ public final class UserProperties implements Parcelable { */ public static final int CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING = 1; + /** + * Possible values for the profile visibility when in quiet mode. This affects the profile data + * and apps surfacing in Settings, sharing surfaces, and file picker surfaces. It signifies + * whether the profile data and apps will be shown or not. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "SHOW_IN_QUIET_MODE_", + value = { + SHOW_IN_QUIET_MODE_PAUSED, + SHOW_IN_QUIET_MODE_HIDDEN, + SHOW_IN_QUIET_MODE_DEFAULT, + } + ) + public @interface ShowInQuietMode { + } + + /** + * Indicates that the profile should still be visible in quiet mode but should be shown as + * paused (e.g. by greying out its icons). + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; + /** + * Indicates that the profile should not be visible when the profile is in quiet mode. + * For example, the profile should not be shown in tabbed views in Settings, files sharing + * surfaces etc when in quiet mode. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; + /** + * Indicates that quiet mode should not have any effect on the profile visibility. If the + * profile is meant to be visible, it will remain visible and vice versa. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; + + /** + * Possible values for the profile apps visibility in sharing surfaces. This indicates the + * profile data and apps should be shown in separate tabs or mixed with its parent user's data + * and apps in sharing surfaces and file picker surfaces. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "SHOW_IN_SHARING_SURFACES_", + value = { + SHOW_IN_SHARING_SURFACES_SEPARATE, + SHOW_IN_SHARING_SURFACES_WITH_PARENT, + SHOW_IN_SHARING_SURFACES_NO, + } + ) + public @interface ShowInSharingSurfaces { + } + + /** + * Indicates that the profile data and apps should be shown in sharing surfaces intermixed with + * parent user's data and apps. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = SHOW_IN_LAUNCHER_WITH_PARENT; + + /** + * Indicates that the profile data and apps should be shown in sharing surfaces separate from + * parent user's data and apps. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_SHARING_SURFACES_SEPARATE = SHOW_IN_LAUNCHER_SEPARATE; + + /** + * Indicates that the profile data and apps should not be shown in sharing surfaces at all. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_SHARING_SURFACES_NO = SHOW_IN_LAUNCHER_NO; /** * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given @@ -331,7 +410,6 @@ public final class UserProperties implements Parcelable { if (hasManagePermission) { // Add items that require MANAGE_USERS or stronger. setShowInSettings(orig.getShowInSettings()); - setHideInSettingsInQuietMode(orig.getHideInSettingsInQuietMode()); setUseParentsContacts(orig.getUseParentsContacts()); setAuthAlwaysRequiredToDisableQuietMode( orig.isAuthAlwaysRequiredToDisableQuietMode()); @@ -343,6 +421,8 @@ public final class UserProperties implements Parcelable { setShowInLauncher(orig.getShowInLauncher()); setMediaSharedWithParent(orig.isMediaSharedWithParent()); setCredentialShareableWithParent(orig.isCredentialShareableWithParent()); + setShowInQuietMode(orig.getShowInQuietMode()); + setShowInSharingSurfaces(orig.getShowInSharingSurfaces()); } /** @@ -419,40 +499,59 @@ public final class UserProperties implements Parcelable { private @ShowInSettings int mShowInSettings; /** - * Returns whether a user should be shown in the Settings app depending on the quiet mode. - * This is generally inapplicable for non-profile users. - * - * <p> {@link #getShowInSettings()} returns whether / how a user should be shown in Settings. - * However, if this behaviour should be changed based on the quiet mode of the user, then this - * property can be used. If the property is not set then the user is shown in the Settings app - * irrespective of whether the user is in quiet mode or not. If the property is set, then the - * user is shown in the Settings app only if the user is not in the quiet mode. Please note that - * this property takes effect only if {@link #getShowInSettings()} does not return - * {@link #SHOW_IN_SETTINGS_NO}. - * - * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this - * property. + * Returns whether a user should be shown in the Settings and sharing surfaces depending on the + * {@link android.os.UserManager#requestQuietModeEnabled(boolean, android.os.UserHandle) + * quiet mode}. This is only applicable to profile users since the quiet mode concept is only + * applicable to profile users. * - * @return true if a profile should be shown in the Settings only when the user is not in the - * quiet mode. + * <p> Please note that, in Settings, this property takes effect only if + * {@link #getShowInSettings()} does not return {@link #SHOW_IN_SETTINGS_NO}. + * Also note that in Sharing surfaces this property takes effect only if + * {@link #getShowInSharingSurfaces()} does not return {@link #SHOW_IN_SHARING_SURFACES_NO}. * - * See also {@link #getShowInSettings()}, {@link #setShowInSettings(int)}, - * {@link ShowInSettings} + * @return One of {@link #SHOW_IN_QUIET_MODE_HIDDEN}, + * {@link #SHOW_IN_QUIET_MODE_PAUSED}, or + * {@link #SHOW_IN_QUIET_MODE_DEFAULT} depending on whether the profile should be + * shown in quiet mode or not. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public @ShowInQuietMode int getShowInQuietMode() { + // NOTE: Launcher currently does not make use of this property. + if (isPresent(INDEX_SHOW_IN_QUIET_MODE)) return mShowInQuietMode; + if (mDefaultProperties != null) return mDefaultProperties.mShowInQuietMode; + throw new SecurityException( + "You don't have permission to query ShowInQuietMode"); + } + /** @hide */ + public void setShowInQuietMode(@ShowInQuietMode int showInQuietMode) { + this.mShowInQuietMode = showInQuietMode; + setPresent(INDEX_SHOW_IN_QUIET_MODE); + } + private int mShowInQuietMode; + + /** + * Returns whether a user's data and apps should be shown in sharing surfaces in a separate tab + * or mixed with the parent user's data/apps. This is only applicable to profile users. * - * @hide + * @return One of {@link #SHOW_IN_SHARING_SURFACES_NO}, + * {@link #SHOW_IN_SHARING_SURFACES_SEPARATE}, or + * {@link #SHOW_IN_SHARING_SURFACES_WITH_PARENT} depending on whether the profile + * should be shown separate from its parent's data, mixed with the parent's data, or + * not shown at all. */ - public boolean getHideInSettingsInQuietMode() { - if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) return mHideInSettingsInQuietMode; - if (mDefaultProperties != null) return mDefaultProperties.mHideInSettingsInQuietMode; + @SuppressLint("UnflaggedApi") // b/306636213 + public @ShowInSharingSurfaces int getShowInSharingSurfaces() { + if (isPresent(INDEX_SHOW_IN_SHARING_SURFACES)) return mShowInSharingSurfaces; + if (mDefaultProperties != null) return mDefaultProperties.mShowInSharingSurfaces; throw new SecurityException( - "You don't have permission to query HideInSettingsInQuietMode"); + "You don't have permission to query ShowInSharingSurfaces"); } /** @hide */ - public void setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) { - this.mHideInSettingsInQuietMode = hideInSettingsInQuietMode; - setPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE); + public void setShowInSharingSurfaces(@ShowInSharingSurfaces int showInSharingSurfaces) { + this.mShowInSharingSurfaces = showInSharingSurfaces; + setPresent(INDEX_SHOW_IN_SHARING_SURFACES); } - private boolean mHideInSettingsInQuietMode; + private int mShowInSharingSurfaces; /** * Returns whether a profile should be started when its parent starts (unless in quiet mode). @@ -799,8 +898,11 @@ public final class UserProperties implements Parcelable { case ATTR_SHOW_IN_SETTINGS: setShowInSettings(parser.getAttributeInt(i)); break; - case ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE: - setHideInSettingsInQuietMode(parser.getAttributeBoolean(i)); + case ATTR_SHOW_IN_QUIET_MODE: + setShowInQuietMode(parser.getAttributeInt(i)); + break; + case ATTR_SHOW_IN_SHARING_SURFACES: + setShowInSharingSurfaces(parser.getAttributeInt(i)); break; case ATTR_INHERIT_DEVICE_POLICY: setInheritDevicePolicy(parser.getAttributeInt(i)); @@ -858,9 +960,12 @@ public final class UserProperties implements Parcelable { if (isPresent(INDEX_SHOW_IN_SETTINGS)) { serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings); } - if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) { - serializer.attributeBoolean(null, ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE, - mHideInSettingsInQuietMode); + if (isPresent(INDEX_SHOW_IN_QUIET_MODE)) { + serializer.attributeInt(null, ATTR_SHOW_IN_QUIET_MODE, + mShowInQuietMode); + } + if (isPresent(INDEX_SHOW_IN_SHARING_SURFACES)) { + serializer.attributeInt(null, ATTR_SHOW_IN_SHARING_SURFACES, mShowInSharingSurfaces); } if (isPresent(INDEX_INHERIT_DEVICE_POLICY)) { serializer.attributeInt(null, ATTR_INHERIT_DEVICE_POLICY, @@ -912,7 +1017,8 @@ public final class UserProperties implements Parcelable { dest.writeInt(mShowInLauncher); dest.writeBoolean(mStartWithParent); dest.writeInt(mShowInSettings); - dest.writeBoolean(mHideInSettingsInQuietMode); + dest.writeInt(mShowInQuietMode); + dest.writeInt(mShowInSharingSurfaces); dest.writeInt(mInheritDevicePolicy); dest.writeBoolean(mUseParentsContacts); dest.writeBoolean(mUpdateCrossProfileIntentFiltersOnOTA); @@ -936,7 +1042,8 @@ public final class UserProperties implements Parcelable { mShowInLauncher = source.readInt(); mStartWithParent = source.readBoolean(); mShowInSettings = source.readInt(); - mHideInSettingsInQuietMode = source.readBoolean(); + mShowInQuietMode = source.readInt(); + mShowInSharingSurfaces = source.readInt(); mInheritDevicePolicy = source.readInt(); mUseParentsContacts = source.readBoolean(); mUpdateCrossProfileIntentFiltersOnOTA = source.readBoolean(); @@ -974,7 +1081,10 @@ public final class UserProperties implements Parcelable { private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT; private boolean mStartWithParent = false; private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT; - private boolean mHideInSettingsInQuietMode = false; + private @ShowInQuietMode int mShowInQuietMode = + SHOW_IN_QUIET_MODE_PAUSED; + private @ShowInSharingSurfaces int mShowInSharingSurfaces = + SHOW_IN_SHARING_SURFACES_SEPARATE; private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO; private boolean mUseParentsContacts = false; private boolean mUpdateCrossProfileIntentFiltersOnOTA = false; @@ -1005,9 +1115,15 @@ public final class UserProperties implements Parcelable { return this; } - /** Sets the value for {@link #mHideInSettingsInQuietMode} */ - public Builder setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) { - mHideInSettingsInQuietMode = hideInSettingsInQuietMode; + /** Sets the value for {@link #mShowInQuietMode} */ + public Builder setShowInQuietMode(@ShowInQuietMode int showInQuietMode) { + mShowInQuietMode = showInQuietMode; + return this; + } + + /** Sets the value for {@link #mShowInSharingSurfaces}. */ + public Builder setShowInSharingSurfaces(@ShowInSharingSurfaces int showInSharingSurfaces) { + mShowInSharingSurfaces = showInSharingSurfaces; return this; } @@ -1081,7 +1197,8 @@ public final class UserProperties implements Parcelable { mShowInLauncher, mStartWithParent, mShowInSettings, - mHideInSettingsInQuietMode, + mShowInQuietMode, + mShowInSharingSurfaces, mInheritDevicePolicy, mUseParentsContacts, mUpdateCrossProfileIntentFiltersOnOTA, @@ -1100,7 +1217,8 @@ public final class UserProperties implements Parcelable { @ShowInLauncher int showInLauncher, boolean startWithParent, @ShowInSettings int showInSettings, - boolean hideInSettingsInQuietMode, + @ShowInQuietMode int showInQuietMode, + @ShowInSharingSurfaces int showInSharingSurfaces, @InheritDevicePolicy int inheritDevicePolicy, boolean useParentsContacts, boolean updateCrossProfileIntentFiltersOnOTA, @CrossProfileIntentFilterAccessControlLevel int crossProfileIntentFilterAccessControl, @@ -1114,7 +1232,8 @@ public final class UserProperties implements Parcelable { setShowInLauncher(showInLauncher); setStartWithParent(startWithParent); setShowInSettings(showInSettings); - setHideInSettingsInQuietMode(hideInSettingsInQuietMode); + setShowInQuietMode(showInQuietMode); + setShowInSharingSurfaces(showInSharingSurfaces); setInheritDevicePolicy(inheritDevicePolicy); setUseParentsContacts(useParentsContacts); setUpdateCrossProfileIntentFiltersOnOTA(updateCrossProfileIntentFiltersOnOTA); diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index bb5fdb714761..a565f6825e7a 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -87,3 +87,10 @@ flag { description: "Feature flag to detect the invisible labels in Launcher Apps" bug: "299586370" } + +flag { + name: "read_install_info" + namespace: "package_manager_service" + description: "Feature flag to read install related information from an APK." + bug: "275658500" +} diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 9ec082ab8eea..6c6b33b8d716 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -42,3 +42,10 @@ flag { description: "Allow using all cpu cores during a user switch." bug: "308105403" } + +flag { + name: "enable_biometrics_to_unlock_private_space" + namespace: "profile_experiences" + description: "Add support to unlock the private space using biometrics" + bug: "312184187" +} diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index ca84b3563561..6a83cee10309 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -682,6 +682,7 @@ public abstract class BatteryConsumer { static class BatteryConsumerDataLayout { private static final Key[] KEY_ARRAY = new Key[0]; + public static final int POWER_MODEL_NOT_INCLUDED = -1; public final String[] customPowerComponentNames; public final int customPowerComponentCount; public final boolean powerModelsIncluded; @@ -713,7 +714,9 @@ public abstract class BatteryConsumer { // Declare the Key for the power component, ignoring other dimensions. perComponentKeys.add( new Key(componentId, PROCESS_STATE_ANY, - powerModelsIncluded ? columnIndex++ : -1, // power model + powerModelsIncluded + ? columnIndex++ + : POWER_MODEL_NOT_INCLUDED, // power model columnIndex++, // power columnIndex++ // usage duration )); @@ -736,7 +739,9 @@ public abstract class BatteryConsumer { perComponentKeys.add( new Key(componentId, processState, - powerModelsIncluded ? columnIndex++ : -1, // power model + powerModelsIncluded + ? columnIndex++ + : POWER_MODEL_NOT_INCLUDED, // power model columnIndex++, // power columnIndex++ // usage duration )); @@ -843,11 +848,27 @@ public abstract class BatteryConsumer { @SuppressWarnings("unchecked") @NonNull + public T addConsumedPower(@PowerComponent int componentId, double componentPower, + @PowerModel int powerModel) { + mPowerComponentsBuilder.addConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED), + componentPower, powerModel); + return (T) this; + } + + @SuppressWarnings("unchecked") + @NonNull public T setConsumedPower(Key key, double componentPower, @PowerModel int powerModel) { mPowerComponentsBuilder.setConsumedPower(key, componentPower, powerModel); return (T) this; } + @SuppressWarnings("unchecked") + @NonNull + public T addConsumedPower(Key key, double componentPower, @PowerModel int powerModel) { + mPowerComponentsBuilder.addConsumedPower(key, componentPower, powerModel); + return (T) this; + } + /** * Sets the amount of drain attributed to the specified custom drain type. * diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index e85b7bf1c91e..16ffaef03121 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -4029,6 +4029,17 @@ public abstract class BatteryStats { } /** + * A helper object passed to various dump... methods to integrate with such objects + * as BatteryUsageStatsProvider. + */ + public interface BatteryStatsDumpHelper { + /** + * Generates BatteryUsageStats based on the specified BatteryStats. + */ + BatteryUsageStats getBatteryUsageStats(BatteryStats batteryStats, boolean detailed); + } + + /** * Dumps the ControllerActivityCounter if it has any data worth dumping. * The order of the arguments in the final check in line is: * @@ -4390,7 +4401,7 @@ public abstract class BatteryStats { * NOTE: all times are expressed in microseconds, unless specified otherwise. */ public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid, - boolean wifiOnly) { + boolean wifiOnly, BatteryStatsDumpHelper dumpHelper) { if (which != BatteryStats.STATS_SINCE_CHARGED) { dumpLine(pw, 0, STAT_NAMES[which], "err", @@ -4652,7 +4663,7 @@ public abstract class BatteryStats { } } - final BatteryUsageStats stats = getBatteryUsageStats(context, true /* detailed */); + final BatteryUsageStats stats = dumpHelper.getBatteryUsageStats(this, true /* detailed */); dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA, formatCharge(stats.getBatteryCapacity()), formatCharge(stats.getConsumedPower()), @@ -5127,7 +5138,7 @@ public abstract class BatteryStats { @SuppressWarnings("unused") public final void dumpLocked(Context context, PrintWriter pw, String prefix, final int which, - int reqUid, boolean wifiOnly) { + int reqUid, boolean wifiOnly, BatteryStatsDumpHelper dumpHelper) { if (which != BatteryStats.STATS_SINCE_CHARGED) { pw.println("ERROR: BatteryStats.dump called for which type " + which @@ -5854,7 +5865,7 @@ public abstract class BatteryStats { pw.println(); - BatteryUsageStats stats = getBatteryUsageStats(context, true /* detailed */); + BatteryUsageStats stats = dumpHelper.getBatteryUsageStats(this, true /* detailed */); stats.dump(pw, prefix); List<UidMobileRadioStats> uidMobileRadioStats = @@ -7642,10 +7653,11 @@ public abstract class BatteryStats { /** * Dumps a human-readable summary of the battery statistics to the given PrintWriter. * - * @param pw a Printer to receive the dump output. + * @param pw a Printer to receive the dump output. */ @SuppressWarnings("unused") - public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart) { + public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart, + BatteryStatsDumpHelper dumpHelper) { synchronized (this) { prepareForDumpLocked(); } @@ -7663,12 +7675,12 @@ public abstract class BatteryStats { } synchronized (this) { - dumpLocked(context, pw, flags, reqUid, filtering); + dumpLocked(context, pw, flags, reqUid, filtering, dumpHelper); } } private void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, - boolean filtering) { + boolean filtering, BatteryStatsDumpHelper dumpHelper) { if (!filtering) { SparseArray<? extends Uid> uidStats = getUidStats(); final int NU = uidStats.size(); @@ -7803,15 +7815,15 @@ public abstract class BatteryStats { pw.println(" System starts: " + getStartCount() + ", currently on battery: " + getIsOnBattery()); dumpLocked(context, pw, "", STATS_SINCE_CHARGED, reqUid, - (flags&DUMP_DEVICE_WIFI_ONLY) != 0); + (flags & DUMP_DEVICE_WIFI_ONLY) != 0, dumpHelper); pw.println(); } } // This is called from BatteryStatsService. @SuppressWarnings("unused") - public void dumpCheckin(Context context, PrintWriter pw, - List<ApplicationInfo> apps, int flags, long histStart) { + public void dumpCheckin(Context context, PrintWriter pw, List<ApplicationInfo> apps, int flags, + long histStart, BatteryStatsDumpHelper dumpHelper) { synchronized (this) { prepareForDumpLocked(); @@ -7829,12 +7841,12 @@ public abstract class BatteryStats { } synchronized (this) { - dumpCheckinLocked(context, pw, apps, flags); + dumpCheckinLocked(context, pw, apps, flags, dumpHelper); } } private void dumpCheckinLocked(Context context, PrintWriter pw, List<ApplicationInfo> apps, - int flags) { + int flags, BatteryStatsDumpHelper dumpHelper) { if (apps != null) { SparseArray<Pair<ArrayList<String>, MutableBoolean>> uids = new SparseArray<>(); for (int i=0; i<apps.size(); i++) { @@ -7881,7 +7893,7 @@ public abstract class BatteryStats { (Object[])lineArgs); } dumpCheckinLocked(context, pw, STATS_SINCE_CHARGED, -1, - (flags&DUMP_DEVICE_WIFI_ONLY) != 0); + (flags & DUMP_DEVICE_WIFI_ONLY) != 0, dumpHelper); } } @@ -7891,7 +7903,7 @@ public abstract class BatteryStats { * @hide */ public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps, - int flags, long histStart) { + int flags, long histStart, BatteryStatsDumpHelper dumpHelper) { final ProtoOutputStream proto = new ProtoOutputStream(fd); prepareForDumpLocked(); @@ -7909,7 +7921,8 @@ public abstract class BatteryStats { proto.write(BatteryStatsProto.END_PLATFORM_VERSION, getEndPlatformVersion()); if ((flags & DUMP_DAILY_ONLY) == 0) { - final BatteryUsageStats stats = getBatteryUsageStats(context, false /* detailed */); + final BatteryUsageStats stats = + dumpHelper.getBatteryUsageStats(this, false /* detailed */); ProportionalAttributionCalculator proportionalAttributionCalculator = new ProportionalAttributionCalculator(context, stats); dumpProtoAppsLocked(proto, stats, apps, proportionalAttributionCalculator); @@ -8856,8 +8869,6 @@ public abstract class BatteryStats { return !tm.isDataCapable(); } - protected abstract BatteryUsageStats getBatteryUsageStats(Context context, boolean detailed); - private boolean shouldHidePowerComponent(int powerComponent) { return powerComponent == BatteryConsumer.POWER_COMPONENT_IDLE || powerComponent == BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index cd52b5c0f7f3..ed3100251040 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -36,6 +36,7 @@ import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -586,7 +587,8 @@ public final class BatteryUsageStats implements Parcelable, Closeable { + "(" + BatteryConsumer.processStateToString(key.processState) + ")"; } printPowerComponent(pw, prefix, label, devicePowerMah, appsPowerMah, - deviceConsumer.getPowerModel(key), + mIncludesPowerModels ? deviceConsumer.getPowerModel(key) + : BatteryConsumer.POWER_MODEL_UNDEFINED, deviceConsumer.getUsageDurationMillis(key)); } } @@ -774,6 +776,15 @@ public final class BatteryUsageStats implements Parcelable, Closeable { super.finalize(); } + @Override + public String toString() { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + dump(pw, ""); + pw.flush(); + return sw.toString(); + } + /** * Builder for BatteryUsageStats. */ diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 330b992ef308..c0d1fb9c6a88 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -131,6 +131,7 @@ interface IUserManager { int getUserBadgeDarkColorResId(int userId); int getUserStatusBarIconResId(int userId); boolean hasBadge(int userId); + int getProfileLabelResId(int userId); boolean isUserUnlocked(int userId); boolean isUserRunning(int userId); boolean isUserForeground(int userId); diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java index 9e5f5399301c..9c11ad433b8f 100644 --- a/core/java/android/os/PowerComponents.java +++ b/core/java/android/os/PowerComponents.java @@ -15,6 +15,7 @@ */ package android.os; +import static android.os.BatteryConsumer.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED; import static android.os.BatteryConsumer.POWER_COMPONENT_ANY; import static android.os.BatteryConsumer.PROCESS_STATE_ANY; import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED; @@ -118,7 +119,7 @@ class PowerComponents { @BatteryConsumer.PowerModel int getPowerModel(BatteryConsumer.Key key) { - if (key.mPowerModelColumnIndex == -1) { + if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { throw new IllegalStateException( "Power model IDs were not requested in the BatteryUsageStatsQuery"); } @@ -468,7 +469,7 @@ class PowerComponents { mMinConsumedPowerThreshold = minConsumedPowerThreshold; for (BatteryConsumer.Key[] keys : mData.layout.keys) { for (BatteryConsumer.Key key : keys) { - if (key.mPowerModelColumnIndex != -1) { + if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED); } } @@ -478,11 +479,19 @@ class PowerComponents { @NonNull public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower, int powerModel) { - if (Math.abs(componentPower) < mMinConsumedPowerThreshold) { - componentPower = 0; - } mData.putDouble(key.mPowerColumnIndex, componentPower); - if (key.mPowerModelColumnIndex != -1) { + if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { + mData.putInt(key.mPowerModelColumnIndex, powerModel); + } + return this; + } + + @NonNull + public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower, + int powerModel) { + mData.putDouble(key.mPowerColumnIndex, + mData.getDouble(key.mPowerColumnIndex) + componentPower); + if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { mData.putInt(key.mPowerModelColumnIndex, powerModel); } return this; @@ -496,9 +505,6 @@ class PowerComponents { */ @NonNull public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) { - if (Math.abs(componentPower) < mMinConsumedPowerThreshold) { - componentPower = 0; - } final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; if (index < 0 || index >= mData.layout.customPowerComponentCount) { throw new IllegalArgumentException( @@ -575,12 +581,12 @@ class PowerComponents { mData.getLong(key.mDurationColumnIndex) + otherData.getLong(otherKey.mDurationColumnIndex)); - if (key.mPowerModelColumnIndex == -1) { + if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { continue; } boolean undefined = false; - if (otherKey.mPowerModelColumnIndex == -1) { + if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { undefined = true; } else { final int powerModel = mData.getInt(key.mPowerModelColumnIndex); @@ -641,19 +647,26 @@ class PowerComponents { */ @NonNull public PowerComponents build() { - mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower()); - for (BatteryConsumer.Key[] keys : mData.layout.keys) { for (BatteryConsumer.Key key : keys) { - if (key.mPowerModelColumnIndex != -1) { + if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) { mData.putInt(key.mPowerModelColumnIndex, BatteryConsumer.POWER_MODEL_UNDEFINED); } } + + if (mMinConsumedPowerThreshold != 0) { + if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) { + mData.putDouble(key.mPowerColumnIndex, 0); + } + } } } + if (mData.getDouble(mData.layout.totalConsumedPowerColumnIndex) == 0) { + mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower()); + } return new PowerComponents(this); } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 08d6e028f08c..ec6d20fbc0f5 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -5693,6 +5693,44 @@ public class UserManager { } /** + * Returns the string/label that should be used to represent the context user. For example, + * this string can represent a profile in tabbed views. This is only applicable to + * {@link #isProfile() profile users}. This string is translated to the device default language. + * + * @return String representing the label for the context user. + * + * @throws android.content.res.Resources.NotFoundException if the user does not have a label + * defined. + * + * @hide + */ + @SystemApi + @SuppressLint("UnflaggedApi") // b/306636213 + @UserHandleAware( + requiresAnyOfPermissionsIfNotCallerProfileGroup = { + Manifest.permission.MANAGE_USERS, + Manifest.permission.QUERY_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}) + public @NonNull String getProfileLabel() { + if (isManagedProfile(mUserId)) { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getResources().getString( + android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB, + () -> getDefaultProfileLabel(mUserId)); + } + return getDefaultProfileLabel(mUserId); + } + + private String getDefaultProfileLabel(int userId) { + try { + final int resourceId = mService.getProfileLabelResId(userId); + return Resources.getSystem().getString(resourceId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * If the user is a {@link UserManager#isProfile profile}, checks if the user * shares media with its parent user (the user that created this profile). * Returns false for any other type of user. diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig new file mode 100644 index 000000000000..597838305a91 --- /dev/null +++ b/core/java/android/service/chooser/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.service.chooser" + +flag { + name: "support_nfc_resolver" + namespace: "systemui" + description: "This flag controls the new NFC 'resolver' activity" + bug: "268089816" +} + diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index f1d35b5b1185..c486b6a6a46e 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -115,6 +115,7 @@ public class ZenModeConfig implements Parcelable { private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = true; private static final boolean DEFAULT_ALLOW_CONV = true; private static final int DEFAULT_ALLOW_CONV_FROM = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; + private static final boolean DEFAULT_ALLOW_PRIORITY_CHANNELS = true; private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false; // Default setting here is 010011101 = 157 private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS = @@ -141,6 +142,7 @@ public class ZenModeConfig implements Parcelable { private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn"; private static final String ALLOW_ATT_CONV = "convos"; private static final String ALLOW_ATT_CONV_FROM = "convosFrom"; + private static final String ALLOW_ATT_CHANNELS = "channels"; private static final String DISALLOW_TAG = "disallow"; private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects"; private static final String STATE_TAG = "state"; @@ -213,7 +215,12 @@ public class ZenModeConfig implements Parcelable { public int allowConversationsFrom = DEFAULT_ALLOW_CONV_FROM; public int user = UserHandle.USER_SYSTEM; public int suppressedVisualEffects = DEFAULT_SUPPRESSED_VISUAL_EFFECTS; + // Note that when the modes_api flag is true, the areChannelsBypassingDnd boolean only tracks + // whether the current user has any priority channels. These channels may bypass DND when + // allowPriorityChannels is true. + // TODO: b/310620812 - Rename to be more accurate when modes_api flag is inlined. public boolean areChannelsBypassingDnd = DEFAULT_CHANNELS_BYPASSING_DND; + public boolean allowPriorityChannels = DEFAULT_ALLOW_PRIORITY_CHANNELS; public int version; public ZenRule manualRule; @@ -221,7 +228,8 @@ public class ZenModeConfig implements Parcelable { public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>(); @UnsupportedAppUsage - public ZenModeConfig() { } + public ZenModeConfig() { + } public ZenModeConfig(Parcel source) { allowCalls = source.readInt() == 1; @@ -250,6 +258,9 @@ public class ZenModeConfig implements Parcelable { areChannelsBypassingDnd = source.readInt() == 1; allowConversations = source.readBoolean(); allowConversationsFrom = source.readInt(); + if (Flags.modesApi()) { + allowPriorityChannels = source.readBoolean(); + } } @Override @@ -284,11 +295,14 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(areChannelsBypassingDnd ? 1 : 0); dest.writeBoolean(allowConversations); dest.writeInt(allowConversationsFrom); + if (Flags.modesApi()) { + dest.writeBoolean(allowPriorityChannels); + } } @Override public String toString() { - return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') + StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') .append("user=").append(user) .append(",allowAlarms=").append(allowAlarms) .append(",allowMedia=").append(allowMedia) @@ -303,9 +317,14 @@ public class ZenModeConfig implements Parcelable { .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom)) .append(",allowConvFrom=").append(ZenPolicy.conversationTypeToString (allowConversationsFrom)) - .append(",suppressedVisualEffects=").append(suppressedVisualEffects) - .append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd) - .append(",\nautomaticRules=").append(rulesToString()) + .append(",suppressedVisualEffects=").append(suppressedVisualEffects); + if (Flags.modesApi()) { + sb.append(",hasPriorityChannels=").append(areChannelsBypassingDnd); + sb.append(",allowPriorityChannels=").append(allowPriorityChannels); + } else { + sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd); + } + return sb.append(",\nautomaticRules=").append(rulesToString()) .append(",\nmanualRule=").append(manualRule) .append(']').toString(); } @@ -385,7 +404,7 @@ public class ZenModeConfig implements Parcelable { if (!(o instanceof ZenModeConfig)) return false; if (o == this) return true; final ZenModeConfig other = (ZenModeConfig) o; - return other.allowAlarms == allowAlarms + boolean eq = other.allowAlarms == allowAlarms && other.allowMedia == allowMedia && other.allowSystem == allowSystem && other.allowCalls == allowCalls @@ -402,10 +421,22 @@ public class ZenModeConfig implements Parcelable { && other.areChannelsBypassingDnd == areChannelsBypassingDnd && other.allowConversations == allowConversations && other.allowConversationsFrom == allowConversationsFrom; + if (Flags.modesApi()) { + return eq && other.allowPriorityChannels == allowPriorityChannels; + } + return eq; } @Override public int hashCode() { + if (Flags.modesApi()) { + return Objects.hash(allowAlarms, allowMedia, allowSystem, allowCalls, + allowRepeatCallers, allowMessages, + allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents, + user, automaticRules, manualRule, + suppressedVisualEffects, areChannelsBypassingDnd, allowConversations, + allowConversationsFrom, allowPriorityChannels); + } return Objects.hash(allowAlarms, allowMedia, allowSystem, allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents, @@ -511,6 +542,10 @@ public class ZenModeConfig implements Parcelable { rt.allowMedia = safeBoolean(parser, ALLOW_ATT_MEDIA, DEFAULT_ALLOW_MEDIA); rt.allowSystem = safeBoolean(parser, ALLOW_ATT_SYSTEM, DEFAULT_ALLOW_SYSTEM); + if (Flags.modesApi()) { + rt.allowPriorityChannels = safeBoolean(parser, ALLOW_ATT_CHANNELS, + DEFAULT_ALLOW_PRIORITY_CHANNELS); + } // migrate old suppressed visual effects fields, if they still exist in the xml Boolean allowWhenScreenOff = unsafeBoolean(parser, ALLOW_ATT_SCREEN_OFF); @@ -584,6 +619,9 @@ public class ZenModeConfig implements Parcelable { out.attributeBoolean(null, ALLOW_ATT_SYSTEM, allowSystem); out.attributeBoolean(null, ALLOW_ATT_CONV, allowConversations); out.attributeInt(null, ALLOW_ATT_CONV_FROM, allowConversationsFrom); + if (Flags.modesApi()) { + out.attributeBoolean(null, ALLOW_ATT_CHANNELS, allowPriorityChannels); + } out.endTag(null, ALLOW_TAG); out.startTag(null, DISALLOW_TAG); @@ -748,6 +786,13 @@ public class ZenModeConfig implements Parcelable { final int system = safeInt(parser, ALLOW_ATT_SYSTEM, ZenPolicy.STATE_UNSET); final int events = safeInt(parser, ALLOW_ATT_EVENTS, ZenPolicy.STATE_UNSET); final int reminders = safeInt(parser, ALLOW_ATT_REMINDERS, ZenPolicy.STATE_UNSET); + if (Flags.modesApi()) { + final int channels = safeInt(parser, ALLOW_ATT_CHANNELS, ZenPolicy.CHANNEL_TYPE_UNSET); + if (channels != ZenPolicy.CHANNEL_TYPE_UNSET) { + builder.allowChannels(channels); + policySet = true; + } + } if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) { builder.allowCalls(calls); @@ -856,6 +901,10 @@ public class ZenModeConfig implements Parcelable { writeZenPolicyState(SHOW_ATT_AMBIENT, policy.getVisualEffectAmbient(), out); writeZenPolicyState(SHOW_ATT_NOTIFICATION_LIST, policy.getVisualEffectNotificationList(), out); + + if (Flags.modesApi()) { + writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getAllowedChannels(), out); + } } private static void writeZenPolicyState(String attr, int val, TypedXmlSerializer out) @@ -869,6 +918,10 @@ public class ZenModeConfig implements Parcelable { if (val != ZenPolicy.CONVERSATION_SENDERS_UNSET) { out.attributeInt(null, attr, val); } + } else if (Flags.modesApi() && Objects.equals(attr, ALLOW_ATT_CHANNELS)) { + if (val != ZenPolicy.CHANNEL_TYPE_UNSET) { + out.attributeInt(null, attr, val); + } } else { if (val != ZenPolicy.STATE_UNSET) { out.attributeInt(null, attr, val); @@ -1044,6 +1097,11 @@ public class ZenModeConfig implements Parcelable { builder.showInNotificationList( (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) == 0); } + + if (Flags.modesApi()) { + builder.allowChannels(allowPriorityChannels ? ZenPolicy.CHANNEL_TYPE_PRIORITY + : ZenPolicy.CHANNEL_TYPE_NONE); + } return builder.build(); } @@ -1169,8 +1227,15 @@ public class ZenModeConfig implements Parcelable { suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; } + int state = defaultPolicy.state; + if (Flags.modesApi()) { + state = Policy.policyState(defaultPolicy.hasPriorityChannels(), + getAllowPriorityChannelsWithDefault(zenPolicy.getAllowedChannels(), + DEFAULT_ALLOW_PRIORITY_CHANNELS)); + } + return new NotificationManager.Policy(priorityCategories, callSenders, - messageSenders, suppressedVisualEffects, defaultPolicy.state, conversationSenders); + messageSenders, suppressedVisualEffects, state, conversationSenders); } private boolean isPriorityCategoryEnabled(int categoryType, Policy policy) { @@ -1208,6 +1273,24 @@ public class ZenModeConfig implements Parcelable { } /** + * Gets whether priority channels are permitted by this channel type, with the specified + * default if the value is unset. This effectively converts the channel enum to a boolean, + * where "true" indicates priority channels are allowed to break through and "false" means + * they are not. + */ + public static boolean getAllowPriorityChannelsWithDefault( + @ZenPolicy.ChannelType int channelType, boolean defaultAllowChannels) { + switch (channelType) { + case ZenPolicy.CHANNEL_TYPE_PRIORITY: + return true; + case ZenPolicy.CHANNEL_TYPE_NONE: + return false; + default: + return defaultAllowChannels; + } + } + + /** * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType */ public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) { @@ -1259,10 +1342,13 @@ public class ZenModeConfig implements Parcelable { priorityConversationSenders = getConversationSendersWithDefault( allowConversationsFrom, priorityConversationSenders); + int state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0; + if (Flags.modesApi()) { + state = Policy.policyState(areChannelsBypassingDnd, allowPriorityChannels); + } + return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders, - suppressedVisualEffects, areChannelsBypassingDnd - ? Policy.STATE_CHANNELS_BYPASSING_DND : 0, - priorityConversationSenders); + suppressedVisualEffects, state, priorityConversationSenders); } /** @@ -1342,6 +1428,9 @@ public class ZenModeConfig implements Parcelable { allowConversationsFrom); if (policy.state != Policy.STATE_UNSET) { areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0; + if (Flags.modesApi()) { + allowPriorityChannels = policy.allowPriorityChannels(); + } } } @@ -2067,7 +2156,11 @@ public class ZenModeConfig implements Parcelable { boolean allowConversations = (policy.priorityConversationSenders & Policy.PRIORITY_CATEGORY_CONVERSATIONS) != 0; boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0; - boolean allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0; + if (Flags.modesApi()) { + areChannelsBypassingDnd = policy.hasPriorityChannels() + && policy.allowPriorityChannels(); + } + boolean allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0; return !allowReminders && !allowCalls && !allowMessages && !allowEvents && !allowRepeatCallers && !areChannelsBypassingDnd && !allowSystem && !allowConversations; @@ -2098,9 +2191,14 @@ public class ZenModeConfig implements Parcelable { * This includes notification, ringer and system sounds */ public static boolean areAllPriorityOnlyRingerSoundsMuted(ZenModeConfig config) { + boolean areChannelsBypassingDnd = config.areChannelsBypassingDnd; + if (Flags.modesApi()) { + areChannelsBypassingDnd = config.areChannelsBypassingDnd + && config.allowPriorityChannels; + } return !config.allowReminders && !config.allowCalls && !config.allowMessages && !config.allowEvents && !config.allowRepeatCallers - && !config.areChannelsBypassingDnd && !config.allowSystem; + && !areChannelsBypassingDnd && !config.allowSystem; } /** diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java index f345d7cb88fd..9538df1db43a 100644 --- a/core/java/android/service/notification/ZenModeDiff.java +++ b/core/java/android/service/notification/ZenModeDiff.java @@ -18,6 +18,7 @@ package android.service.notification; import android.annotation.IntDef; import android.annotation.Nullable; +import android.app.Flags; import android.util.ArrayMap; import android.util.ArraySet; @@ -221,6 +222,7 @@ public class ZenModeDiff { public static final String FIELD_ALLOW_CONVERSATIONS_FROM = "allowConversationsFrom"; public static final String FIELD_SUPPRESSED_VISUAL_EFFECTS = "suppressedVisualEffects"; public static final String FIELD_ARE_CHANNELS_BYPASSING_DND = "areChannelsBypassingDnd"; + public static final String FIELD_ALLOW_PRIORITY_CHANNELS = "allowPriorityChannels"; private static final Set<String> PEOPLE_TYPE_FIELDS = Set.of(FIELD_ALLOW_CALLS_FROM, FIELD_ALLOW_MESSAGES_FROM); @@ -297,6 +299,12 @@ public class ZenModeDiff { addField(FIELD_ARE_CHANNELS_BYPASSING_DND, new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd)); } + if (Flags.modesApi()) { + if (from.allowPriorityChannels != to.allowPriorityChannels) { + addField(FIELD_ALLOW_PRIORITY_CHANNELS, + new FieldDiff<>(from.allowPriorityChannels, to.allowPriorityChannels)); + } + } // Compare automatic and manual rules final ArraySet<String> allRules = new ArraySet<>(); diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index bffa660cdbd9..3a4a0c5dcd30 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -16,9 +16,11 @@ package android.service.notification; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Flags; import android.app.Notification; import android.app.NotificationChannel; import android.os.Parcel; @@ -44,6 +46,7 @@ public final class ZenPolicy implements Parcelable { private @PeopleType int mPriorityMessages = PEOPLE_TYPE_UNSET; private @PeopleType int mPriorityCalls = PEOPLE_TYPE_UNSET; private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET; + private @ChannelType int mAllowChannels = CHANNEL_TYPE_UNSET; /** @hide */ @IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = { @@ -213,6 +216,36 @@ public final class ZenPolicy implements Parcelable { public static final int STATE_DISALLOW = 2; /** @hide */ + @IntDef(prefix = { "CHANNEL_TYPE_" }, value = { + CHANNEL_TYPE_UNSET, + CHANNEL_TYPE_PRIORITY, + CHANNEL_TYPE_NONE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ChannelType {} + + /** + * Indicates no explicit setting for which channels may bypass DND when this policy is active. + * Defaults to {@link #CHANNEL_TYPE_PRIORITY}. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int CHANNEL_TYPE_UNSET = 0; + + /** + * Indicates that channels marked as {@link NotificationChannel#canBypassDnd()} can bypass DND + * when this policy is active. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int CHANNEL_TYPE_PRIORITY = 1; + + /** + * Indicates that no channels can bypass DND when this policy is active, even those marked as + * {@link NotificationChannel#canBypassDnd()}. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int CHANNEL_TYPE_NONE = 2; + + /** @hide */ public ZenPolicy() { mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0)); mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0)); @@ -396,6 +429,19 @@ public final class ZenPolicy implements Parcelable { } /** + * Which types of {@link NotificationChannel channels} this policy allows to bypass DND. When + * this value is {@link #CHANNEL_TYPE_PRIORITY priority} channels, any channel with + * canBypassDnd() may bypass DND; when it is {@link #CHANNEL_TYPE_NONE none}, even channels + * with canBypassDnd() will be intercepted. + * @return {@link #CHANNEL_TYPE_UNSET}, {@link #CHANNEL_TYPE_PRIORITY}, or + * {@link #CHANNEL_TYPE_NONE} + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public @ChannelType int getAllowedChannels() { + return mAllowChannels; + } + + /** * Whether this policy hides all visual effects * @hide */ @@ -795,6 +841,15 @@ public final class ZenPolicy implements Parcelable { } return this; } + + /** + * Set whether priority channels are permitted to break through DND. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public @NonNull Builder allowChannels(@ChannelType int channelType) { + mZenPolicy.mAllowChannels = channelType; + return this; + } } @Override @@ -809,6 +864,9 @@ public final class ZenPolicy implements Parcelable { dest.writeInt(mPriorityCalls); dest.writeInt(mPriorityMessages); dest.writeInt(mConversationSenders); + if (Flags.modesApi()) { + dest.writeInt(mAllowChannels); + } } public static final @android.annotation.NonNull Parcelable.Creator<ZenPolicy> CREATOR = @@ -825,6 +883,9 @@ public final class ZenPolicy implements Parcelable { policy.mPriorityCalls = source.readInt(); policy.mPriorityMessages = source.readInt(); policy.mConversationSenders = source.readInt(); + if (Flags.modesApi()) { + policy.mAllowChannels = source.readInt(); + } return policy; } @@ -836,16 +897,18 @@ public final class ZenPolicy implements Parcelable { @Override public String toString() { - return new StringBuilder(ZenPolicy.class.getSimpleName()) + StringBuilder sb = new StringBuilder(ZenPolicy.class.getSimpleName()) .append('{') .append("priorityCategories=[").append(priorityCategoriesToString()) .append("], visualEffects=[").append(visualEffectsToString()) .append("], priorityCallsSenders=").append(peopleTypeToString(mPriorityCalls)) .append(", priorityMessagesSenders=").append(peopleTypeToString(mPriorityMessages)) .append(", priorityConversationSenders=").append( - conversationTypeToString(mConversationSenders)) - .append('}') - .toString(); + conversationTypeToString(mConversationSenders)); + if (Flags.modesApi()) { + sb.append(", allowChannels=").append(channelTypeToString(mAllowChannels)); + } + return sb.append('}').toString(); } // Returns a list containing the first maxLength elements of the input list if the list is @@ -975,21 +1038,45 @@ public final class ZenPolicy implements Parcelable { return "invalidConversationType{" + conversationType + "}"; } + /** + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static String channelTypeToString(@ChannelType int channelType) { + switch (channelType) { + case CHANNEL_TYPE_UNSET: + return "unset"; + case CHANNEL_TYPE_PRIORITY: + return "priority"; + case CHANNEL_TYPE_NONE: + return "none"; + } + return "invalidChannelType{" + channelType + "}"; + } + @Override public boolean equals(@Nullable Object o) { if (!(o instanceof ZenPolicy)) return false; if (o == this) return true; final ZenPolicy other = (ZenPolicy) o; - return Objects.equals(other.mPriorityCategories, mPriorityCategories) + boolean eq = Objects.equals(other.mPriorityCategories, mPriorityCategories) && Objects.equals(other.mVisualEffects, mVisualEffects) && other.mPriorityCalls == mPriorityCalls && other.mPriorityMessages == mPriorityMessages && other.mConversationSenders == mConversationSenders; + if (Flags.modesApi()) { + return eq && other.mAllowChannels == mAllowChannels; + } + return eq; } @Override public int hashCode() { + if (Flags.modesApi()) { + return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, + mPriorityMessages, mConversationSenders, mAllowChannels); + } return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages, mConversationSenders); } @@ -1064,7 +1151,10 @@ public final class ZenPolicy implements Parcelable { } /** - * Applies another policy on top of this policy + * Applies another policy on top of this policy. For each field, the resulting policy will have + * most restrictive setting that is set of the two policies (if only one has a field set, the + * result will inherit that policy's setting). + * * @hide */ public void apply(ZenPolicy policyToApply) { @@ -1107,6 +1197,15 @@ public final class ZenPolicy implements Parcelable { mVisualEffects.set(visualEffect, policyToApply.mVisualEffects.get(visualEffect)); } } + + // apply allowed channels + if (Flags.modesApi()) { + // if no channels are allowed, can't newly allow them + if (mAllowChannels != CHANNEL_TYPE_NONE + && policyToApply.mAllowChannels != CHANNEL_TYPE_UNSET) { + mAllowChannels = policyToApply.mAllowChannels; + } + } } /** @@ -1139,9 +1238,10 @@ public final class ZenPolicy implements Parcelable { /** * Converts a policy to a statsd proto. - * @hides + * @hide */ public byte[] toProto() { + // TODO: b/308672510 - log new ZenPolicy fields to DNDPolicyProto. ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ProtoOutputStream proto = new ProtoOutputStream(bytes); diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 42203d4b0d71..c716cd2e4a9c 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -387,7 +387,6 @@ public class VoiceInteractionService extends Service { VoiceInteractionService::onShutdownInternal, VoiceInteractionService.this)); }; - private void onShutdownInternal() { onShutdown(); // Stop any active recognitions when shutting down. @@ -1025,6 +1024,26 @@ public class VoiceInteractionService extends Service { } } + /** Set sandboxed detection training data egress op. + * + * <p> This method can be called by a preinstalled assistant to allow/disallow training data + * egress from trusted process. + * + * @return whether was able to update sandboxed detection op successfully. + * @throws SecurityException if assistant is not a preinstalled assistant. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS) + public boolean setSandboxedDetectionTrainingDataOp(int opMode) { + Log.i(TAG, "Setting training data egress op-mode to " + opMode); + try { + return mSystemService.setSandboxedDetectionTrainingDataOp(opMode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the * pre-bundled system voice models. diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 9f886c826174..d131dc9a4c7d 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -69,6 +69,7 @@ public final class InputDevice implements Parcelable { private final String mName; private final int mVendorId; private final int mProductId; + private final int mDeviceBus; private final String mDescriptor; private final InputDeviceIdentifier mIdentifier; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) @@ -468,8 +469,8 @@ public final class InputDevice implements Parcelable { * Called by native code */ private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, - int productId, String descriptor, boolean isExternal, int sources, int keyboardType, - KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag, + int productId, int deviceBus, String descriptor, boolean isExternal, int sources, + int keyboardType, KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag, @Nullable String keyboardLayoutType, boolean hasVibrator, boolean hasMicrophone, boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, int usiVersionMajor, int usiVersionMinor, int associatedDisplayId) { @@ -479,6 +480,7 @@ public final class InputDevice implements Parcelable { mName = name; mVendorId = vendorId; mProductId = productId; + mDeviceBus = deviceBus; mDescriptor = descriptor; mIsExternal = isExternal; mSources = sources; @@ -512,6 +514,7 @@ public final class InputDevice implements Parcelable { mName = in.readString(); mVendorId = in.readInt(); mProductId = in.readInt(); + mDeviceBus = in.readInt(); mDescriptor = in.readString(); mIsExternal = in.readInt() != 0; mSources = in.readInt(); @@ -551,6 +554,7 @@ public final class InputDevice implements Parcelable { private String mName = ""; private int mVendorId = 0; private int mProductId = 0; + private int mDeviceBus = 0; private String mDescriptor = ""; private boolean mIsExternal = false; private int mSources = 0; @@ -604,6 +608,12 @@ public final class InputDevice implements Parcelable { return this; } + /** @see InputDevice#getDeviceBus() */ + public Builder setDeviceBus(int deviceBus) { + mDeviceBus = deviceBus; + return this; + } + /** @see InputDevice#getDescriptor() */ public Builder setDescriptor(String descriptor) { mDescriptor = descriptor; @@ -705,6 +715,7 @@ public final class InputDevice implements Parcelable { mName, mVendorId, mProductId, + mDeviceBus, mDescriptor, mIsExternal, mSources, @@ -847,6 +858,21 @@ public final class InputDevice implements Parcelable { } /** + * Gets the device bus used by given device, if available. + * <p> + * The device bus is the communication system used for transferring data + * (e.g. USB, Bluetooth etc.). This value comes from the kernel (from input.h). + * A value of 0 will be assigned where the device bus is not available. + * </p> + * + * @return The device bus of a given device + * @hide + */ + public int getDeviceBus() { + return mDeviceBus; + } + + /** * Gets the input device descriptor, which is a stable identifier for an input device. * <p> * An input device descriptor uniquely identifies an input device. Its value @@ -1448,6 +1474,7 @@ public final class InputDevice implements Parcelable { out.writeString(mName); out.writeInt(mVendorId); out.writeInt(mProductId); + out.writeInt(mDeviceBus); out.writeString(mDescriptor); out.writeInt(mIsExternal ? 1 : 0); out.writeInt(mSources); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 8befe8a2be85..23d0de32874f 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -859,33 +859,47 @@ public final class SurfaceControl implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"FRAME_RATE_SELECTION_STRATEGY_"}, - value = {FRAME_RATE_SELECTION_STRATEGY_SELF, - FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN}) + value = {FRAME_RATE_SELECTION_STRATEGY_PROPAGATE, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN, + FRAME_RATE_SELECTION_STRATEGY_SELF}) public @interface FrameRateSelectionStrategy {} // From window.h. Keep these in sync. /** * Default value. The layer uses its own frame rate specifications, assuming it has any - * specifications, instead of its parent's. + * specifications, instead of its parent's. If it does not have its own frame rate + * specifications, it will try to use its parent's. It will propagate its specifications to any + * descendants that do not have their own. + * * However, {@link #FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} on an ancestor layer - * supersedes this behavior, meaning that this layer will inherit the frame rate specifications - * of that ancestor layer. + * supersedes this behavior, meaning that this layer will inherit frame rate specifications + * regardless of whether it has its own. * @hide */ - public static final int FRAME_RATE_SELECTION_STRATEGY_SELF = 0; + public static final int FRAME_RATE_SELECTION_STRATEGY_PROPAGATE = 0; /** * The layer's frame rate specifications will propagate to and override those of its descendant * layers. - * The layer with this strategy has the {@link #FRAME_RATE_SELECTION_STRATEGY_SELF} behavior - * for itself. This does mean that any parent or ancestor layer that also has the strategy - * {@link FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} will override this layer's + * + * The layer itself has the {@link #FRAME_RATE_SELECTION_STRATEGY_PROPAGATE} behavior. + * Thus, ancestor layer that also has the strategy + * {@link #FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} will override this layer's * frame rate specifications. * @hide */ public static final int FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1; /** + * The layer's frame rate specifications will not propagate to its descendant + * layers, even if the descendant layer has no frame rate specifications. + * However, {@link #FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN} on an ancestor + * layer supersedes this behavior. + * @hide + */ + public static final int FRAME_RATE_SELECTION_STRATEGY_SELF = 2; + + /** * Builder class for {@link SurfaceControl} objects. * * By default the surface will be hidden, and have "unset" bounds, meaning it can diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7b456007e4ae..7a6c2929c706 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -130,7 +130,6 @@ import android.graphics.FrameInfo; import android.graphics.HardwareRenderer; import android.graphics.HardwareRenderer.FrameDrawingCallback; import android.graphics.HardwareRendererObserver; -import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; @@ -206,7 +205,6 @@ import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; -import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.ContentCaptureSession; @@ -4029,56 +4027,20 @@ public final class ViewRootImpl implements ViewParent, } private void notifyContentCaptureEvents() { - try { - if (!isContentCaptureEnabled()) { - if (DEBUG_CONTENT_CAPTURE) { - Log.d(mTag, "notifyContentCaptureEvents while disabled"); - } - mAttachInfo.mContentCaptureEvents = null; - return; - } - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents"); - } - MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager - .getMainContentCaptureSession(); - for (int i = 0; i < mAttachInfo.mContentCaptureEvents.size(); i++) { - int sessionId = mAttachInfo.mContentCaptureEvents.keyAt(i); - mainSession.notifyViewTreeEvent(sessionId, /* started= */ true); - ArrayList<Object> events = mAttachInfo.mContentCaptureEvents - .valueAt(i); - for_each_event: for (int j = 0; j < events.size(); j++) { - Object event = events.get(j); - if (event instanceof AutofillId) { - mainSession.notifyViewDisappeared(sessionId, (AutofillId) event); - } else if (event instanceof View) { - View view = (View) event; - ContentCaptureSession session = view.getContentCaptureSession(); - if (session == null) { - Log.w(mTag, "no content capture session on view: " + view); - continue for_each_event; - } - int actualId = session.getId(); - if (actualId != sessionId) { - Log.w(mTag, "content capture session mismatch for view (" + view - + "): was " + sessionId + " before, it's " + actualId + " now"); - continue for_each_event; - } - ViewStructure structure = session.newViewStructure(view); - view.onProvideContentCaptureStructure(structure, /* flags= */ 0); - session.notifyViewAppeared(structure); - } else if (event instanceof Insets) { - mainSession.notifyViewInsetsChanged(sessionId, (Insets) event); - } else { - Log.w(mTag, "invalid content capture event: " + event); - } - } - mainSession.notifyViewTreeEvent(sessionId, /* started= */ false); + if (!isContentCaptureEnabled()) { + if (DEBUG_CONTENT_CAPTURE) { + Log.d(mTag, "notifyContentCaptureEvents while disabled"); } mAttachInfo.mContentCaptureEvents = null; - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + return; + } + + final ContentCaptureManager manager = mAttachInfo.mContentCaptureManager; + if (manager != null && mAttachInfo.mContentCaptureEvents != null) { + final MainContentCaptureSession session = manager.getMainContentCaptureSession(); + session.notifyContentCaptureEvents(mAttachInfo.mContentCaptureEvents); } + mAttachInfo.mContentCaptureEvents = null; } private void notifyHolderSurfaceDestroyed() { diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 5a058ff3de99..a8297472445f 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -18,6 +18,7 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureHelper.toSet; +import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -52,6 +53,7 @@ import android.view.contentcapture.ContentCaptureSession.FlushReason; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.RingBuffer; import com.android.internal.util.SyncResultReceiver; @@ -495,10 +497,9 @@ public final class ContentCaptureManager { @GuardedBy("mLock") private int mFlags; - // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler - // held at the Application level - @NonNull - private final Handler mHandler; + @Nullable + @GuardedBy("mLock") + private Handler mHandler; @GuardedBy("mLock") private MainContentCaptureSession mMainSession; @@ -562,11 +563,6 @@ public final class ContentCaptureManager { if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName()); - // TODO(b/119220549): we might not even need a handler, as the IPCs are oneway. But if we - // do, then we should optimize it to run the tests after the Choreographer finishes the most - // important steps of the frame. - mHandler = Handler.createAsync(Looper.getMainLooper()); - mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager(); if (mOptions.contentProtectionOptions.enableReceiver @@ -594,13 +590,27 @@ public final class ContentCaptureManager { public MainContentCaptureSession getMainContentCaptureSession() { synchronized (mLock) { if (mMainSession == null) { - mMainSession = new MainContentCaptureSession(mContext, this, mHandler, mService); + mMainSession = new MainContentCaptureSession( + mContext, this, prepareContentCaptureHandler(), mService); if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); } return mMainSession; } } + @NonNull + @GuardedBy("mLock") + private Handler prepareContentCaptureHandler() { + if (mHandler == null) { + if (runOnBackgroundThreadEnabled()) { + mHandler = BackgroundThread.getHandler(); + } else { + mHandler = Handler.createAsync(Looper.getMainLooper()); + } + } + return mHandler; + } + /** @hide */ @UiThread public void onActivityCreated(@NonNull IBinder applicationToken, diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index d9b0f8035a6d..14ec14bf7cfc 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -34,7 +34,6 @@ import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALS import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UiThread; import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.graphics.Insets; @@ -50,7 +49,10 @@ import android.text.Spannable; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; +import android.util.SparseArray; import android.util.TimeUtils; +import android.view.View; +import android.view.ViewStructure; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; import android.view.contentprotection.ContentProtectionEventProcessor; @@ -58,6 +60,7 @@ import android.view.inputmethod.BaseInputConnection; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; +import com.android.modules.expresslog.Counter; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -66,6 +69,7 @@ import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; /** * Main session associated with a context. @@ -79,6 +83,9 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private static final String TAG = MainContentCaptureSession.class.getSimpleName(); + private static final String CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID = + "content_capture.value_content_capture_wrong_thread_count"; + // For readability purposes... private static final boolean FORCE_FLUSH = true; @@ -163,6 +170,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @Nullable private final LocalLog mFlushHistory; + private final AtomicInteger mWrongThreadCount = new AtomicInteger(0); + /** * Binder object used to update the session state. */ @@ -207,7 +216,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } else { binder = null; } - mainSession.mHandler.post(() -> mainSession.onSessionStarted(resultCode, binder)); + mainSession.mHandler.post(() -> + mainSession.onSessionStarted(resultCode, binder)); } } @@ -244,9 +254,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** * Starts this session. */ - @UiThread void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags) { + runOnContentCaptureThread(() -> startImpl(token, shareableActivityToken, component, flags)); + } + + private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, + @NonNull ComponentName component, int flags) { + checkOnContentCaptureThread(); if (!isContentCaptureEnabled()) return; if (sVerbose) { @@ -280,17 +295,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e); } } - @Override void onDestroy() { - mHandler.removeMessages(MSG_FLUSH); - mHandler.post(() -> { + clearAndRunOnContentCaptureThread(() -> { try { flush(FLUSH_REASON_SESSION_FINISHED); } finally { destroySession(); } - }); + }, MSG_FLUSH); } /** @@ -302,8 +315,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - @UiThread public void onSessionStarted(int resultCode, @Nullable IBinder binder) { + checkOnContentCaptureThread(); if (binder != null) { mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); mDirectServiceVulture = () -> { @@ -347,13 +360,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - @UiThread public void sendEvent(@NonNull ContentCaptureEvent event) { sendEvent(event, /* forceFlush= */ false); } - @UiThread private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { + checkOnContentCaptureThread(); final int eventType = event.getType(); if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED @@ -396,15 +408,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } - @UiThread private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) { + checkOnContentCaptureThread(); if (mContentProtectionEventProcessor != null) { mContentProtectionEventProcessor.processEvent(event); } } - @UiThread private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { + checkOnContentCaptureThread(); final int eventType = event.getType(); final int maxBufferSize = mManager.mOptions.maxBufferSize; if (mEvents == null) { @@ -538,13 +550,13 @@ public final class MainContentCaptureSession extends ContentCaptureSession { flush(flushReason); } - @UiThread private boolean hasStarted() { + checkOnContentCaptureThread(); return mState != UNKNOWN_STATE; } - @UiThread private void scheduleFlush(@FlushReason int reason, boolean checkExisting) { + checkOnContentCaptureThread(); if (sVerbose) { Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) + ", checkExisting=" + checkExisting); @@ -588,8 +600,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs); } - @UiThread private void flushIfNeeded(@FlushReason int reason) { + checkOnContentCaptureThread(); if (mEvents == null || mEvents.isEmpty()) { if (sVerbose) Log.v(TAG, "Nothing to flush"); return; @@ -600,8 +612,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Override - @UiThread public void flush(@FlushReason int reason) { + runOnContentCaptureThread(() -> flushImpl(reason)); + } + + private void flushImpl(@FlushReason int reason) { + checkOnContentCaptureThread(); if (mEvents == null || mEvents.size() == 0) { if (sVerbose) { Log.v(TAG, "Don't flush for empty event buffer."); @@ -669,8 +685,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * Resets the buffer and return a {@link ParceledListSlice} with the previous events. */ @NonNull - @UiThread private ParceledListSlice<ContentCaptureEvent> clearEvents() { + checkOnContentCaptureThread(); // NOTE: we must save a reference to the current mEvents and then set it to to null, // otherwise clearing it would clear it in the receiving side if the service is also local. if (mEvents == null) { @@ -684,14 +700,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { /** hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - @UiThread public void destroySession() { + checkOnContentCaptureThread(); if (sDebug) { Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " + getDebugState()); } + reportWrongThreadMetric(); try { mSystemServerInterface.finishSession(mId); } catch (RemoteException e) { @@ -710,8 +727,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // clearings out. /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - @UiThread public void resetSession(int newState) { + checkOnContentCaptureThread(); if (sVerbose) { Log.v(TAG, "handleResetSession(" + getActivityName() + "): from " + getStateAsString(mState) + " to " + getStateAsString(newState)); @@ -794,24 +811,26 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // change should also get get rid of the "internalNotifyXXXX" methods above void notifyChildSessionStarted(int parentSessionId, int childSessionId, @NonNull ContentCaptureContext clientContext) { - mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) + runOnContentCaptureThread( + () -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) .setParentSessionId(parentSessionId).setClientContext(clientContext), FORCE_FLUSH)); } void notifyChildSessionFinished(int parentSessionId, int childSessionId) { - mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) + runOnContentCaptureThread( + () -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) .setParentSessionId(parentSessionId), FORCE_FLUSH)); } void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { - mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) + runOnContentCaptureThread(() -> + sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) .setViewNode(node.mNode))); } - /** Public because is also used by ViewRootImpl */ - public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) { - mHandler.post(() -> sendEvent( + void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) { + runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id))); } @@ -836,52 +855,102 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final int startIndex = Selection.getSelectionStart(text); final int endIndex = Selection.getSelectionEnd(text); - mHandler.post(() -> sendEvent( + runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED) .setAutofillId(id).setText(eventText) .setComposingIndex(composingStart, composingEnd) .setSelectionIndex(startIndex, endIndex))); } - /** Public because is also used by ViewRootImpl */ - public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { - mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) + void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) { + runOnContentCaptureThread(() -> + sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED) .setInsets(viewInsets))); } - /** Public because is also used by ViewRootImpl */ - public void notifyViewTreeEvent(int sessionId, boolean started) { + void notifyViewTreeEvent(int sessionId, boolean started) { final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled(); - mHandler.post(() -> sendEvent( + runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, type), disableFlush ? !started : FORCE_FLUSH)); } void notifySessionResumed(int sessionId) { - mHandler.post(() -> sendEvent( + runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH)); } void notifySessionPaused(int sessionId) { - mHandler.post(() -> sendEvent( + runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH)); } void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { - mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) + runOnContentCaptureThread(() -> + sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) .setClientContext(context), FORCE_FLUSH)); } /** public because is also used by ViewRootImpl */ public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) { - mHandler.post(() -> sendEvent( + runOnContentCaptureThread(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED) .setBounds(bounds) )); } + /** public because is also used by ViewRootImpl */ + public void notifyContentCaptureEvents( + @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { + runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents)); + } + + private void notifyContentCaptureEventsImpl( + @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) { + checkOnContentCaptureThread(); + try { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "notifyContentCaptureEvents"); + } + for (int i = 0; i < contentCaptureEvents.size(); i++) { + int sessionId = contentCaptureEvents.keyAt(i); + notifyViewTreeEvent(sessionId, /* started= */ true); + ArrayList<Object> events = contentCaptureEvents.valueAt(i); + for_each_event: for (int j = 0; j < events.size(); j++) { + Object event = events.get(j); + if (event instanceof AutofillId) { + notifyViewDisappeared(sessionId, (AutofillId) event); + } else if (event instanceof View) { + View view = (View) event; + ContentCaptureSession session = view.getContentCaptureSession(); + if (session == null) { + Log.w(TAG, "no content capture session on view: " + view); + continue for_each_event; + } + int actualId = session.getId(); + if (actualId != sessionId) { + Log.w(TAG, "content capture session mismatch for view (" + view + + "): was " + sessionId + " before, it's " + actualId + " now"); + continue for_each_event; + } + ViewStructure structure = session.newViewStructure(view); + view.onProvideContentCaptureStructure(structure, /* flags= */ 0); + session.notifyViewAppeared(structure); + } else if (event instanceof Insets) { + notifyViewInsetsChanged(sessionId, (Insets) event); + } else { + Log.w(TAG, "invalid content capture event: " + event); + } + } + notifyViewTreeEvent(sessionId, /* started= */ false); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + @Override void dump(@NonNull String prefix, @NonNull PrintWriter pw) { super.dump(prefix, pw); @@ -960,17 +1029,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { return getDebugState() + ", reason=" + getFlushReasonAsString(reason); } - @UiThread private boolean isContentProtectionReceiverEnabled() { return mManager.mOptions.contentProtectionOptions.enableReceiver; } - @UiThread private boolean isContentCaptureReceiverEnabled() { return mManager.mOptions.enableReceiver; } - @UiThread private boolean isContentProtectionEnabled() { // Should not be possible for mComponentName to be null here but check anyway // Should not be possible for groups to be empty if receiver is enabled but check anyway @@ -980,4 +1046,49 @@ public final class MainContentCaptureSession extends ContentCaptureSession { && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); } + + /** + * Checks that the current work is running on the assigned thread from {@code mHandler} and + * count the number of times running on the wrong thread. + * + * <p>It is not guaranteed that the callers always invoke function from a single thread. + * Therefore, accessing internal properties in {@link MainContentCaptureSession} should + * always delegate to the assigned thread from {@code mHandler} for synchronization.</p> + */ + private void checkOnContentCaptureThread() { + final boolean onContentCaptureThread = mHandler.getLooper().isCurrentThread(); + if (!onContentCaptureThread) { + mWrongThreadCount.incrementAndGet(); + Log.e(TAG, "MainContentCaptureSession running on " + Thread.currentThread()); + } + } + + /** Reports number of times running on the wrong thread. */ + private void reportWrongThreadMetric() { + Counter.logIncrement( + CONTENT_CAPTURE_WRONG_THREAD_METRIC_ID, mWrongThreadCount.getAndSet(0)); + } + + /** + * Ensures that {@code r} will be running on the assigned thread. + * + * <p>This is to prevent unnecessary delegation to Handler that results in fragmented runnable. + * </p> + */ + private void runOnContentCaptureThread(@NonNull Runnable r) { + if (!mHandler.getLooper().isCurrentThread()) { + mHandler.post(r); + } else { + r.run(); + } + } + + private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) { + if (!mHandler.getLooper().isCurrentThread()) { + mHandler.removeMessages(what); + mHandler.post(r); + } else { + r.run(); + } + } } diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java index aaf90bd00535..858401a9ec1d 100644 --- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java @@ -18,7 +18,6 @@ package android.view.contentprotection; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UiThread; import android.content.ContentCaptureOptions; import android.content.pm.ParceledListSlice; import android.os.Handler; @@ -102,7 +101,6 @@ public class ContentProtectionEventProcessor { } /** Main entry point for {@link ContentCaptureEvent} processing. */ - @UiThread public void processEvent(@NonNull ContentCaptureEvent event) { if (EVENT_TYPES_TO_STORE.contains(event.getType())) { storeEvent(event); @@ -112,7 +110,6 @@ public class ContentProtectionEventProcessor { } } - @UiThread private void storeEvent(@NonNull ContentCaptureEvent event) { // Ensure receiver gets the package name which might not be set ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode(); @@ -121,7 +118,6 @@ public class ContentProtectionEventProcessor { mEventBuffer.append(event); } - @UiThread private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) { ViewNode viewNode = event.getViewNode(); String eventText = ContentProtectionUtils.getEventTextLower(event); @@ -154,7 +150,6 @@ public class ContentProtectionEventProcessor { } } - @UiThread private void loginDetected() { if (mLastFlushTime == null || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) { @@ -163,13 +158,11 @@ public class ContentProtectionEventProcessor { resetLoginFlags(); } - @UiThread private void resetLoginFlags() { mGroupsAll.forEach(group -> group.mFound = false); mAnyGroupFound = false; } - @UiThread private void maybeResetLoginFlags() { if (mAnyGroupFound) { if (mResetLoginRemainingEventsToProcess <= 0) { @@ -183,7 +176,6 @@ public class ContentProtectionEventProcessor { } } - @UiThread private void flush() { mLastFlushTime = Instant.now(); @@ -192,7 +184,6 @@ public class ContentProtectionEventProcessor { mHandler.post(() -> handlerOnLoginDetected(events)); } - @UiThread @NonNull private ParceledListSlice<ContentCaptureEvent> clearEvents() { List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray()); diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index 1bc735397115..d4cfd63492fc 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -428,16 +428,25 @@ public interface ImeTracker { ImeTracker LOGGER = new ImeTracker() { { - // Set logging flag initial value. - mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false); - // Update logging flag dynamically. - SystemProperties.addChangeCallback(() -> - mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false)); + // Read initial system properties. + reloadSystemProperties(); + // Update when system properties change. + SystemProperties.addChangeCallback(this::reloadSystemProperties); } - /** Whether progress should be logged. */ + /** Whether {@link #onProgress} calls should be logged. */ private boolean mLogProgress; + /** Whether the stack trace at the request call site should be logged. */ + private boolean mLogStackTrace; + + private void reloadSystemProperties() { + mLogProgress = SystemProperties.getBoolean( + "persist.debug.imetracker", false); + mLogStackTrace = SystemProperties.getBoolean( + "persist.debug.imerequest.logstacktrace", false); + } + @NonNull @Override public Token onRequestShow(@Nullable String component, int uid, @Origin int origin, @@ -447,7 +456,8 @@ public interface ImeTracker { reason); Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin) - + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)); + + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason), + mLogStackTrace ? new Throwable() : null); return token; } @@ -461,7 +471,8 @@ public interface ImeTracker { reason); Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin) - + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)); + + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason), + mLogStackTrace ? new Throwable() : null); return token; } diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java index 1b9ff44c9185..8e89541647c3 100644 --- a/core/java/android/webkit/WebViewDelegate.java +++ b/core/java/android/webkit/WebViewDelegate.java @@ -16,6 +16,8 @@ package android.webkit; +import static android.webkit.Flags.updateServiceV2; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -205,6 +207,9 @@ public final class WebViewDelegate { * Returns whether WebView should run in multiprocess mode. */ public boolean isMultiProcessEnabled() { + if (updateServiceV2()) { + return true; + } try { return WebViewFactory.getUpdateService().isMultiProcessEnabled(); } catch (RemoteException e) { diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index bc7a5fda6f7a..e14ae72ee7a5 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -16,6 +16,8 @@ package android.webkit; +import static android.webkit.Flags.updateServiceV2; + import android.content.pm.PackageInfo; import android.os.Build; import android.os.ChildZygoteProcess; @@ -50,8 +52,8 @@ public class WebViewZygote { private static PackageInfo sPackage; /** - * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote - * will not be started. + * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote will + * not be started. Should be removed entirely after we remove the updateServiceV2 flag. */ @GuardedBy("sLock") private static boolean sMultiprocessEnabled = false; @@ -73,11 +75,19 @@ public class WebViewZygote { public static boolean isMultiprocessEnabled() { synchronized (sLock) { - return sMultiprocessEnabled && sPackage != null; + if (updateServiceV2()) { + return sPackage != null; + } else { + return sMultiprocessEnabled && sPackage != null; + } } } public static void setMultiprocessEnabled(boolean enabled) { + if (updateServiceV2()) { + throw new IllegalStateException( + "setMultiprocessEnabled shouldn't be called if update_service_v2 flag is set."); + } synchronized (sLock) { sMultiprocessEnabled = enabled; diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java index 5b0d8d1233c6..cc2329fc47cb 100644 --- a/core/java/android/window/SystemPerformanceHinter.java +++ b/core/java/android/window/SystemPerformanceHinter.java @@ -20,7 +20,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN; -import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF; +import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE; import android.annotation.IntDef; import android.annotation.NonNull; @@ -303,7 +303,7 @@ public class SystemPerformanceHinter { SurfaceControl displaySurfaceControl = mDisplayRootProvider.getRootForDisplay( session.displayId); mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl, - FRAME_RATE_SELECTION_STRATEGY_SELF); + FRAME_RATE_SELECTION_STRATEGY_PROPAGATE); // smoothSwitchOnly is false to request a higher framerate, even if it means switching // the display mode will cause would jank on non-VRR devices because keeping a lower // refresh rate would mean a poorer user experience. diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java index 35ce72620d09..34c639974bfd 100644 --- a/core/java/android/window/WindowInfosListenerForTest.java +++ b/core/java/android/window/WindowInfosListenerForTest.java @@ -19,6 +19,7 @@ package android.window; import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.graphics.Matrix; import android.graphics.Rect; @@ -87,6 +88,38 @@ public class WindowInfosListenerForTest { @NonNull public final Matrix transform; + /** + * True if the window is touchable. + */ + @SuppressLint("UnflaggedApi") // The API is only used for tests. + public final boolean isTouchable; + + /** + * True if the window is focusable. + */ + @SuppressLint("UnflaggedApi") // The API is only used for tests. + public final boolean isFocusable; + + /** + * True if the window is preventing splitting + */ + @SuppressLint("UnflaggedApi") // The API is only used for tests. + public final boolean isPreventSplitting; + + /** + * True if the window duplicates touches received to wallpaper. + */ + @SuppressLint("UnflaggedApi") // The API is only used for tests. + public final boolean isDuplicateTouchToWallpaper; + + /** + * True if the window is listening for when there is a touch DOWN event + * occurring outside its touchable bounds. When such an event occurs, + * this window will receive a MotionEvent with ACTION_OUTSIDE. + */ + @SuppressLint("UnflaggedApi") // The API is only used for tests. + public final boolean isWatchOutsideTouch; + WindowInfo(@NonNull IBinder windowToken, @NonNull String name, int displayId, @NonNull Rect bounds, int inputConfig, @NonNull Matrix transform) { this.windowToken = windowToken; @@ -96,6 +129,14 @@ public class WindowInfosListenerForTest { this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0; this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0; this.transform = transform; + this.isTouchable = (inputConfig & InputConfig.NOT_TOUCHABLE) == 0; + this.isFocusable = (inputConfig & InputConfig.NOT_FOCUSABLE) == 0; + this.isPreventSplitting = (inputConfig + & InputConfig.PREVENT_SPLITTING) != 0; + this.isDuplicateTouchToWallpaper = (inputConfig + & InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER) != 0; + this.isWatchOutsideTouch = (inputConfig + & InputConfig.WATCH_OUTSIDE_TOUCH) != 0; } @Override diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig index 94e6009f4cd6..f828cff14e88 100644 --- a/core/java/android/window/flags/responsible_apis.aconfig +++ b/core/java/android/window/flags/responsible_apis.aconfig @@ -26,4 +26,18 @@ flag { namespace: "responsible_apis" description: "Enable toasts to indicate actual BAL blocking." bug: "308059069" -}
\ No newline at end of file +} + +flag { + name: "bal_return_correct_code_if_caller_is_persistent_system_process" + namespace: "responsible_apis" + description: "Split visibility check and return a better status code in case of system process." + bug: "171459802" +} + +flag { + name: "bal_improve_real_caller_visibility_check" + namespace: "responsible_apis" + description: "Prevent a task to restart based on a visible window during task switch." + bug: "171459802" +} diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 0da03fb5aaeb..29932f342b74 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -35,8 +35,8 @@ flag { flag { namespace: "window_surfaces" - name: "remove_capture_display" - description: "Remove uses of ScreenCapture#captureDisplay" + name: "delete_capture_display" + description: "Delete uses of ScreenCapture#captureDisplay" is_fixed_read_only: true bug: "293445881" } @@ -48,3 +48,11 @@ flag { is_fixed_read_only: true bug: "262477923" } + +flag { + namespace: "window_surfaces" + name: "secure_window_state" + description: "Move SC secure flag to WindowState level" + is_fixed_read_only: true + bug: "308662081" +} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 4705dc5db90b..31a3ebd9b32e 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -8,6 +8,13 @@ flag { } flag { + name: "edge_to_edge_by_default" + namespace: "windowing_frontend" + description: "Make app go edge-to-edge by default when targeting SDK 35 or greater" + bug: "309578419" +} + +flag { name: "defer_display_updates" namespace: "window_manager" description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running" diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 68e2b48c8f08..ea4fc3910d89 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -388,4 +388,14 @@ interface IVoiceInteractionManagerService { oneway void notifyActivityEventChanged( in IBinder activityToken, int type); + + /** + * Sets the sandboxed detection training data egress op to provided op-mode. + * Caller must be the active assistant and a preinstalled assistant. + * + * @param opMode app-op mode to set training data egress op to. + * + * @return whether was able to successfully set training data egress op. + */ + boolean setSandboxedDetectionTrainingDataOp(int opMode); } diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java index e55c64199f45..f5fe12eb66c0 100644 --- a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java +++ b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java @@ -37,11 +37,22 @@ public class RefreshRateSettingsUtils { * @return The highest refresh rate */ public static float findHighestRefreshRateForDefaultDisplay(Context context) { + return findHighestRefreshRate(context, Display.DEFAULT_DISPLAY); + } + + /** + * Find the highest refresh rate among all the modes of the specified display. + * + * @param context The context + * @param displayId The display ID + * @return The highest refresh rate + */ + public static float findHighestRefreshRate(Context context, int displayId) { final DisplayManager dm = context.getSystemService(DisplayManager.class); - final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY); + final Display display = dm.getDisplay(displayId); if (display == null) { - Log.w(TAG, "No valid default display device"); + Log.w(TAG, "No valid display device with ID = " + displayId); return DEFAULT_REFRESH_RATE; } diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java index 6f114e34b21c..661628a8c148 100644 --- a/core/java/com/android/internal/os/MonotonicClock.java +++ b/core/java/com/android/internal/os/MonotonicClock.java @@ -30,6 +30,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -49,6 +50,8 @@ public class MonotonicClock { private final Clock mClock; private long mTimeshift; + public static final long UNDEFINED = -1; + public MonotonicClock(File file) { mFile = new AtomicFile(file); mClock = Clock.SYSTEM_CLOCK; @@ -98,14 +101,11 @@ public class MonotonicClock { return; } - mFile.write(out -> { - try { - writeXml(out, Xml.newBinarySerializer()); - out.close(); - } catch (IOException e) { - Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e); - } - }); + try (FileOutputStream out = mFile.startWrite()) { + writeXml(out, Xml.newBinarySerializer()); + } catch (IOException e) { + Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e); + } } /** diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageHidden.java index 1fafdf9035e6..0d7b43368821 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java +++ b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageHidden.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.parsing.pkg; +package com.android.internal.pm.parsing.pkg; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; @@ -22,16 +22,18 @@ import android.content.pm.PackageInfo; /** * Methods that normal consumers should not have access to. This usually means the field is stateful - * or deprecated and should be access through {@link AndroidPackageUtils} or a system manager - * class. + * or deprecated and should be access through + * {@link com.android.server.pm.parsing.pkg.AndroidPackageUtils} or a system manager class. * <p> * This is a separate interface, not implemented by the base {@link AndroidPackage} because Java * doesn't support non-public interface methods. The class must be cast to this interface. * <p> * Because they exist in different packages, some methods are duplicated from * android.content.pm.parsing.ParsingPackageHidden. + * @hide */ -interface AndroidPackageHidden { +// TODO: remove public after moved PackageImpl and AndroidPackageUtils +public interface AndroidPackageHidden { /** * @see ApplicationInfo#primaryCpuAbi diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageInternal.java index 9eca7d651dea..6f8e658da50c 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java +++ b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageInternal.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.parsing.pkg; +package com.android.internal.pm.parsing.pkg; import android.annotation.NonNull; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java index 85f8f7609c2a..7ef0b4864c84 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java +++ b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.parsing.pkg; +package com.android.internal.pm.parsing.pkg; import android.content.pm.SigningDetails; diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java b/core/java/com/android/internal/pm/pkg/AndroidPackageSplitImpl.java index c0f2c25a66a4..3c564e9f401e 100644 --- a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplitImpl.java +++ b/core/java/com/android/internal/pm/pkg/AndroidPackageSplitImpl.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package com.android.server.pm.pkg; +package com.android.internal.pm.pkg; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; +import com.android.server.pm.pkg.AndroidPackageSplit; + import java.util.Collections; import java.util.List; import java.util.Objects; @@ -94,8 +96,8 @@ public class AndroidPackageSplitImpl implements AndroidPackageSplit { if (this == o) return true; if (!(o instanceof AndroidPackageSplitImpl)) return false; AndroidPackageSplitImpl that = (AndroidPackageSplitImpl) o; - var fieldsEqual = mRevisionCode == that.mRevisionCode && mFlags == that.mFlags && Objects.equals( - mName, that.mName) && Objects.equals(mPath, that.mPath) + var fieldsEqual = mRevisionCode == that.mRevisionCode && mFlags == that.mFlags + && Objects.equals(mName, that.mName) && Objects.equals(mPath, that.mPath) && Objects.equals(mClassLoaderName, that.mClassLoaderName); if (!fieldsEqual) return false; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java index 099c67654250..4ed361f24439 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.parsing; +package com.android.internal.pm.pkg.parsing; import android.annotation.CallSuper; import android.annotation.NonNull; @@ -32,6 +32,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.R; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.pm.pkg.component.ParsedAttribution; @@ -43,7 +44,6 @@ import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.pm.pkg.component.ParsedUsesPermission; -import com.android.server.pm.parsing.pkg.ParsedPackage; import java.security.PublicKey; import java.util.List; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageHidden.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageHidden.java index 9c1c9ac9efcd..5758fd7cc579 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageHidden.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageHidden.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.parsing; +package com.android.internal.pm.pkg.parsing; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 6c17e9e58c63..dd310dc3922a 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -175,6 +175,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) private static final long NAV_BAR_COLOR_DEFAULT_TRANSPARENT = 232195501L; + /** + * Make app go edge-to-edge by default if the target SDK is + * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or above. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + private static final long EDGE_TO_EDGE_BY_DEFAULT = 309578419; + private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES | (1 << FEATURE_CUSTOM_TITLE) | (1 << FEATURE_CONTENT_TRANSITIONS) | @@ -387,7 +395,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mAllowFloatingWindowsFillScreen = context.getResources().getBoolean( com.android.internal.R.bool.config_allowFloatingWindowsFillScreen); mDefaultEdgeToEdge = - context.getApplicationInfo().targetSdkVersion >= DEFAULT_EDGE_TO_EDGE_SDK_VERSION; + context.getApplicationInfo().targetSdkVersion >= DEFAULT_EDGE_TO_EDGE_SDK_VERSION + || (CompatChanges.isChangeEnabled(EDGE_TO_EDGE_BY_DEFAULT) + && Flags.edgeToEdgeByDefault()); if (mDefaultEdgeToEdge) { mDecorFitsSystemWindows = false; } @@ -2558,11 +2568,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mNavigationBarColor = navBarColor == navBarDefaultColor + && !mDefaultEdgeToEdge && !(CompatChanges.isChangeEnabled(NAV_BAR_COLOR_DEFAULT_TRANSPARENT) && Flags.navBarTransparentByDefault()) && !context.getResources().getBoolean( R.bool.config_navBarDefaultTransparent) - && !mDefaultEdgeToEdge ? navBarCompatibleColor : navBarColor; @@ -3895,6 +3905,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void setStatusBarColor(int color) { + if (mStatusBarColor == color && mForcedStatusBarColor) { + return; + } mStatusBarColor = color; mForcedStatusBarColor = true; if (mDecor != null) { @@ -3913,6 +3926,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void setNavigationBarColor(int color) { + if (mNavigationBarColor == color && mForcedNavigationBarColor) { + return; + } mNavigationBarColor = color; mForcedNavigationBarColor = true; if (mDecor != null) { diff --git a/core/java/com/android/server/pm/OWNERS b/core/java/com/android/server/pm/OWNERS new file mode 100644 index 000000000000..6ef34e2e5d4b --- /dev/null +++ b/core/java/com/android/server/pm/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 36137 + +file:/PACKAGE_MANAGER_OWNERS + diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java index 99819c82c5df..4e4f26cd6ad6 100644 --- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -58,7 +58,6 @@ import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.pm.pkg.component.ParsedUsesPermission; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.security.PublicKey; import java.util.List; @@ -691,7 +690,7 @@ public interface AndroidPackage { /** * The names of packages to adopt ownership of permissions from, parsed under {@link - * ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}. + * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}. * * @see R.styleable#AndroidManifestOriginalPackage_name * @hide @@ -796,7 +795,7 @@ public interface AndroidPackage { /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link - * ParsingPackageUtils#TAG_KEY_SETS}. + * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_KEY_SETS}. * * @see R.styleable#AndroidManifestKeySet * @see R.styleable#AndroidManifestPublicKey @@ -905,7 +904,7 @@ public interface AndroidPackage { * For system use to migrate from an old package name to a new one, moving over data if * available. * - * @see R.styleable#AndroidManifestOriginalPackage} + * @see R.styleable#AndroidManifestOriginalPackage * @hide */ @NonNull @@ -1267,7 +1266,7 @@ public interface AndroidPackage { /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link - * ParsingPackageUtils#TAG_KEY_SETS}. + * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_KEY_SETS}. * * @see R.styleable#AndroidManifestUpgradeKeySet * @hide @@ -1417,6 +1416,7 @@ public interface AndroidPackage { * @see ApplicationInfo#FLAG_IS_GAME * @see R.styleable#AndroidManifestApplication_isGame * @hide + * @deprecated */ @Deprecated boolean isGame(); diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java b/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java index 9024c27ebecb..9024c27ebecb 100644 --- a/services/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java +++ b/core/java/com/android/server/pm/pkg/AndroidPackageSplit.java diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp index 262f5e8e761e..239c6260800b 100644 --- a/core/jni/android_view_InputDevice.cpp +++ b/core/jni/android_view_InputDevice.cpp @@ -81,7 +81,8 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi deviceInfo.getId(), deviceInfo.getGeneration(), deviceInfo.getControllerNumber(), nameObj.get(), static_cast<int32_t>(ident.vendor), - static_cast<int32_t>(ident.product), descriptorObj.get(), + static_cast<int32_t>(ident.product), + static_cast<int32_t>(ident.bus), descriptorObj.get(), deviceInfo.isExternal(), deviceInfo.getSources(), deviceInfo.getKeyboardType(), kcmObj.get(), keyboardLanguageTagObj.get(), keyboardLayoutTypeObj.get(), @@ -111,7 +112,7 @@ int register_android_view_InputDevice(JNIEnv* env) gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz); gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>", - "(IIILjava/lang/String;IILjava/lang/" + "(IIILjava/lang/String;IIILjava/lang/" "String;ZIILandroid/view/KeyCharacterMap;Ljava/" "lang/String;Ljava/lang/String;ZZZZZIII)V"); diff --git a/core/res/OWNERS b/core/res/OWNERS index f24c3f59155a..332ad2a82d38 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -47,6 +47,10 @@ per-file res/values/config_device_idle.xml = file:/apex/jobscheduler/OWNERS # Wear per-file res/*-watch/* = file:/WEAR_OWNERS +# Peformance +per-file res/values/config.xml = file:/PERFORMANCE_OWNERS +per-file res/values/symbols.xml = file:/PERFORMANCE_OWNERS + # PowerProfile per-file res/xml/power_profile.xml = file:/BATTERY_STATS_OWNERS per-file res/xml/power_profile_test.xml = file:/BATTERY_STATS_OWNERS diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index f9ab7dd283c9..f5b828922165 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Dit kan jou interaksies met \'n app of \'n hardewaresensor naspoor en namens jou met apps interaksie hê."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Laat toe"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Weier"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Deïnstalleer"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"’n App verberg die toestemmingversoek en jou antwoord kan dus nie geverifieer word nie."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tik op \'n kenmerk om dit te begin gebruik:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Kies kenmerke om saam met die toeganklikheidknoppie te gebruik"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Kies kenmerke om saam met die volumesleutelkortpad te gebruik"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Naweek"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Geleentheid"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Slaap"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aan"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Af"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> demp sekere klanke"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Daar is \'n interne probleem met jou toestel en dit sal dalk onstabiel wees totdat jy \'n fabriekterugstelling doen."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Daar is \'n interne probleem met jou toestel. Kontak jou vervaardiger vir besonderhede."</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 5ac3225274d4..fbef501115b6 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ከመተግበሪያ ጋር ወይም የሃርድዌር ዳሳሽ ጋር እርስዎ ያልዎትን መስተጋብሮች ዱካ መከታተል እና በእርስዎ ምትክ ከመተግበሪያዎች ጋር መስተጋብር መፈጸም ይችላል።"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ፍቀድ"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ከልክል"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"አራግፍ"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"አንድ መተግበሪያ የፍቃድ ጥያቄውን እያደበዘዘ ነው ስለዚህ የእርስዎ ምላሽ ሊረጋገጥ አይችልም።"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"አንድ ባህሪን መጠቀም ለመጀመር መታ ያድርጉት፦"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"በተደራሽነት አዝራር የሚጠቀሙባቸው ባሕሪያት ይምረጡ"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"በድምጽ ቁልፍ አቋራጭ የሚጠቀሙባቸው ባሕሪያት ይምረጡ"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"የሳምንት እረፍት ቀናት"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ክስተት"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"መተኛት"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"በርቷል"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ጠፍቷል"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> አንዳንድ ድምጾችን እየዘጋ ነው"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"መሣሪያዎ ላይ የውስጣዊ ችግር አለ፣ የፋብሪካ ውሂብ ዳግም እስኪያስጀምሩት ድረስ ላይረጋጋ ይችላል።"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"መሣሪያዎ ላይ የውስጣዊ ችግር አለ። ዝርዝሮችን ለማግኘት አምራችዎን ያነጋግሩ።"</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"ማይክሮፎን ታግዷል"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ወደ ማሳያ ማንጸባረቅ አልተቻለም"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"የተለየ ገመድ ይጠቀሙ እና እንደገና ይሞክሩ"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"መሳሪያዎ በጣም ሞቃት ነው እና እስኪቀዘቅዝ ድረስ ማሳያውን ማንጸባረቅ አይችልም"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ገመድ ማሳያዎችን ላይደግፍ ይችላል"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"የእርስዎ USB-C ገመድ ከማሳያዎች ጋር በትክክል ላይገናኝ ይችላል"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 537beece8cfa..9ca2d1996156 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1714,10 +1714,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"قد يؤدي ذلك إلى السماح للميزة بتتبّع تفاعلاتك مع تطبيق أو جهاز استشعار والتفاعل مع التطبيقات نيابةً عنك."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"سماح"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"رفض"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"إلغاء التثبيت"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"تعذَّر التحقّق من ردّك بسبب حجب أحد التطبيقات طلب الحصول على الإذن."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"انقر على ميزة لبدء استخدامها:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"اختيار الميزات التي تريد استخدامها مع زر أدوات تمكين الوصول"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"اختيار الميزات التي تريد استخدامها مع اختصار مفتاح التحكّم في مستوى الصوت"</string> @@ -1912,10 +1910,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"نهاية الأسبوع"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"حدث"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"النوم"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"مفعَّل"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"غير مفعَّل"</string> <string name="muted_by" msgid="91464083490094950">"يعمل <xliff:g id="THIRD_PARTY">%1$s</xliff:g> على كتم بعض الأصوات."</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"حدثت مشكلة داخلية في جهازك، وقد لا يستقر وضعه حتى إجراء إعادة الضبط على الإعدادات الأصلية."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"حدثت مشكلة داخلية في جهازك. يمكنك الاتصال بالمصنِّع للحصول على تفاصيل."</string> @@ -2348,8 +2344,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"تم حظر الميكروفون."</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"يتعذّر إجراء نسخ مطابق لمحتوى جهازك إلى الشاشة"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"يُرجى استخدام كابل آخر وإعادة المحاولة."</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"جهازك ساخن للغاية ولا يمكنه إجراء نسخ مطابق للمحتوى إلى الشاشة إلى أن تنخفض حرارته."</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"قد لا يتوافق الكابل مع الشاشات"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"قد لا يتم توصيل الكابل المزوَّد بمنفذ USB-C بالشاشات بشكل صحيح."</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index a5d2ee415a3f..7473f4924220 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ই আপুনি কোনো এপ্ বা হার্ডৱেৰ ছেন্সৰৰ সৈতে কৰা ভাব-বিনিময় আৰু আপোনাৰ হৈ অন্য কোনো লোকে এপৰ সৈতে কৰা ভাব-বিনিময় ট্ৰেক কৰিব পাৰে।"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"অনুমতি দিয়ক"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"অস্বীকাৰ কৰক"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"আনইনষ্টল কৰক"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"এটা এপে অনুমতিৰ অনুৰোধটো অস্পষ্ট কৰি আছে আৰু সেয়েহে আপোনাৰ সঁহাৰিটো সত্যাপন কৰিব নোৱাৰি।"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"কোনো এটা সুবিধা ব্যৱহাৰ কৰিবলৈ সেইটোত টিপক:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"সাধ্য-সুবিধা বুটামটোৰ জৰিয়তে ব্যৱহাৰ কৰিবলৈ সুবিধাসমূহ বাছনি কৰক"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ভলিউম কীৰ শ্বৰ্টকাটটোৰ জৰিয়তে ব্যৱহাৰ কৰিবলৈ সুবিধাসমূহ বাছনি কৰক"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"সপ্তাহ অন্ত"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"কার্যক্ৰম"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"নিদ্ৰাৰত"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"অন আছে"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"অফ আছে"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>এ কিছুমান ধ্বনি মিউট কৰি আছে"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"আপোনাৰ ডিভাইচত এটা আভ্যন্তৰীণ সমস্যা আছে আৰু আপুনি ফেক্টৰী ডেটা ৰিছেট নকৰালৈকে ই সুস্থিৰভাৱে কাম নকৰিব পাৰে।"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"আপোনাৰ ডিভাইচত এটা আভ্যন্তৰীণ সমস্যা আছে। সবিশেষ জানিবৰ বাবে আপোনাৰ ডিভাইচ নির্মাতাৰ সৈতে যোগাযোগ কৰক।"</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্ৰ’ফ’নটো অৱৰোধ কৰি থোৱা আছে"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"সংযুক্ত ডিছপ্লে’ উপলব্ধ নহয়"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য এডাল কে’বল ব্যৱহাৰ কৰি পুনৰ চেষ্টা কৰক"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"আপোনাৰ ডিভাইচটো অত্যধিক গৰম হৈছে আৰু এইটো ঠাণ্ডা নোহোৱালৈকে ডিছপ্লে’ত প্ৰতিবিম্বকৰণ কৰিব নোৱাৰি"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কে’বলে ডিছপ্লে’ সমৰ্থন নকৰিবও পাৰে"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপোনাৰ USB-C কে’বল ডিছপ্লে’ৰ সৈতে সঠিকভাৱে সংযোগ নহ’বও পাৰে"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 87e1c9f28fff..b1002e2cdd5d 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Tətbiq və sensorlarla əlaqələrinizi izləyib tətbiqlərə adınızdan əmrlər verə bilər."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"İcazə verin"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"İmtina edin"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Sistemdən silin"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Bir tətbiq icazə sorğusunu gizlətdiyi üçün cavabı yoxlamaq mümkün deyil."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Funksiyanı istifadə etmək üçün onun üzərinə toxunun:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Xüsusi imkanlar düyməsinin köməyilə işə salınacaq funksiyaları seçin"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Səs səviyyəsi düyməsinin qısayolu ilə istifadə edəcəyiniz funksiyaları seçin"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Həftə sonu"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Tədbir"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Yuxu vaxtı"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aktiv"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Deaktiv"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> bəzi səsləri səssiz rejimə salır"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Cihazınızın daxili problemi var və istehsalçı sıfırlanması olmayana qədər qeyri-stabil ola bilər."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Cihazınızın daxili problemi var. Əlavə məlumat üçün istehsalçı ilə əlaqə saxlayın."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon blok edilib"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeydə əks etdirmək olmur"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Başqa kabel istifadə edin və yenidən cəhd edin"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Cihaz çox isinib və soyuyana qədər displeydə əks etdirmək olmur"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeyləri dəstəkləməyə bilər"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabeli displeylərə düzgün qoşulmaya bilər"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"İkili ekran"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 06d73eedbaa9..196818a755c3 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Može da prati interakcije sa aplikacijom ili senzorom hardvera i koristi aplikacije umesto vas."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Dozvoli"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Odbij"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Deinstaliraj"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikacija krije zahtev za dozvolu, pa odgovor ne može da se verifikuje."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Dodirnite neku funkciju da biste počeli da je koristite:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Odaberite funkcije koje ćete koristiti sa dugmetom Pristupačnost"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Odaberite funkcije za prečicu tasterom jačine zvuka"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Vikend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Događaj"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spavanje"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Uključeno"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Isključeno"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvuke"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Došlo je do internog problema u vezi sa uređajem i možda će biti nestabilan dok ne obavite resetovanje na fabrička podešavanja."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Došlo je do internog problema u vezi sa uređajem. Potražite detalje od proizvođača."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Preslikavanje na ekran nije moguće"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrebite drugi kabl i probajte ponovo"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Uređaj je previše zagrejan, pa ne može da se preslikava na ekran dok se ne ohladi"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl ne podržava ekrane"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se ne povezuje pravilno sa ekranima"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 7ff6838951fb..d7efa8ac6597 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1712,10 +1712,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Гэта функцыя можа адсочваць вашы ўзаемадзеянні з праграмай ці датчыкам апаратнага забеспячэння і ўзаемадзейнічаць з праграмамі ад вашага імя."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Дазволіць"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Адмовіць"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Выдаліць"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Праграма хавае запыт дазволу, таму ваш адказ немагчыма спраўдзіць."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Каб пачаць выкарыстоўваць функцыю, націсніце на яе:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Выберыце функцыі, якія будзеце выкарыстоўваць з кнопкай спецыяльных магчымасцей"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Выберыце функцыі для выкарыстання з клавішай гучнасці"</string> @@ -1910,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Выхадныя"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Падзея"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Рэжым сну"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Уключана"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Выключана"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> выключае некаторыя гукі"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"На вашай прыладзе ўзнікла ўнутраная праблема, і яна можа працаваць нестабільна, пакуль вы не зробіце скід да заводскіх налад."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"На вашай прыладзе ўзнікла ўнутраная праблема. Для атрымання дадатковай інфармацыі звярніцеся да вытворцы."</string> @@ -2346,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрафон заблакіраваны"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не ўдалося прадубліраваць змесціва на дысплэі"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Паспрабуйце скарыстаць іншы кабель"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Прылада занадта моцна нагрэлася і таму не можа дубліраваць змесціва на дысплэі, пакуль не астыне"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Магчыма, кабель несумяшчальны з дысплэямі"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Магчыма, кабель USB-C не падыходзіць да дысплэяў"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 304c2a0d8fae..b9e1db4816c7 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Услугата може да проследява взаимодействията ви с дадено приложение или хардуерен сензор, както и да взаимодейства с приложенията от ваше име."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Разреш."</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Отказ"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Деинсталиране"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Отговорът ви не може да бъде потвърден, тъй като приложение прикрива заявката за разрешение."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Докоснете дадена функция, за да започнете да я използвате:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Избиране на функции, които да използвате с бутона за достъпност"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Избиране на функции, които да използвате с прекия път чрез бутона за силата на звука"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Събота и неделя"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Събитие"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Време за сън"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Вкл."</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Изкл."</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> заглушава някои звуци"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Възникна вътрешен проблем с устройството ви. То може да е нестабилно, докато не възстановите фабричните настройки."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Възникна вътрешен проблем с устройството ви. За подробности се свържете с производителя."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонът е блокиран"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се копира огледално на дисплея"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Използвайте друг кабел и опитайте отново"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Устройството ви е твърде топло и няма да може да дублира на екрана, преди да се охлади"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелът не поддържа дисплеи"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелът ви може да не се свързва правилно с дисплеи"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 1c6fa8da2bb4..669c99e935a0 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -1908,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"সপ্তাহান্ত"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ইভেন্ট"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ঘুমানোর সময়"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"চালু আছে"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"বন্ধ আছে"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> কিছু সাউন্ডকে মিউট করে দিচ্ছে"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"আপনার ডিভাইসে একটি অভ্যন্তরীন সমস্যা হয়েছে, এবং আপনি যতক্ষণ না পর্যন্ত এটিকে ফ্যাক্টরি ডেটা রিসেট করছেন ততক্ষণ এটি ঠিকভাবে কাজ নাও করতে পারে৷"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"আপনার ডিভাইসে একটি অভ্যন্তরীন সমস্যা হয়েছে৷ বিস্তারিত জানার জন্য প্রস্তুতকারকের সাথে যোগাযোগ করুন৷"</string> @@ -2344,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্রোফোন ব্লক করা হয়েছে"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ডিসপ্লে মিরর করা যাচ্ছে না"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য কোনও কেবল ব্যবহার করে আবার চেষ্টা করুন"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"আপনার ডিভাইস খুব গরম হয়ে আছে এবং সেটি ঠাণ্ডা না হওয়া পর্যন্ত ডিসপ্লে মিরর করা যাবে না"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কেবল, ডিসপ্লের সাথে কাজ নাও করতে পারে"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপনার USB-C কেবল, ডিসপ্লেতে সঠিকভাবে কানেক্ট নাও হতে পারে"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index c3f431a32ea5..9dcd8693f886 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Može pratiti vaše interakcije s aplikacijom ili hardverskim senzorom te ostvariti interakciju s aplikacijama umjesto vas."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Dozvoli"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Odbij"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Deinstaliraj"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikacija skriva zahtjev za odobrenje, pa se vaš odgovor ne može potvrditi."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Dodirnite funkciju da je počnete koristiti:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Odaberite funkcije koje ćete koristiti s dugmetom Pristupačnost"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Odaberite funkcije koje ćete koristiti pomoću prečice tipke za jačinu zvuka"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Vikend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Događaj"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spavanje"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Uključeno"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Isključeno"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvukove"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Postoji problem u vašem uređaju i može biti nestabilan dok ga ne vratite na fabričke postavke."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Postoji problem u vašem uređaju. Za više informacija obratite se proizvođaču."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nije moguće preslikati na ekran"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabl i pokušajte ponovo"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Uređaj je pregrijan i ne može preslikavati na ekran dok se ne ohladi"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl možda neće podržavati ekrane"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se možda neće pravilno povezati s ekranima"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index fbbd2daf5dec..66e76ac083e9 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Pot fer un seguiment de les teves interaccions amb una aplicació o un sensor de maquinari, i interaccionar amb aplicacions en nom teu."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permet"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Denega"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstal·la"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Una aplicació està ocultant la sol·licitud de permís, de manera que la teva resposta no es pot verificar"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toca una funció per començar a utilitzar-la:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Tria les funcions que vols utilitzar amb el botó d\'accessibilitat"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Tria les funcions que vols utilitzar amb la drecera per a tecles de volum"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Cap de setmana"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Esdeveniment"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Mentre dormo"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activat"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desactivat"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> està silenciant alguns sons"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"S\'ha produït un error intern al dispositiu i és possible que funcioni de manera inestable fins que restableixis les dades de fàbrica."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"S\'ha produït un error intern al dispositiu. Contacta amb el fabricant del dispositiu per obtenir més informació."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"El micròfon està bloquejat"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No es pot projectar a la pantalla"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilitza un altre cable i torna-ho a provar"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"El dispositiu està massa calent i no pot projectar a la pantalla fins que es refredi"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"És possible que el cable no sigui compatible amb pantalles"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"És possible que el teu cable USB-C no es connecti correctament a les pantalles"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Pantalla dual"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 959b7bc0ceb7..1acb8d44b890 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1910,10 +1910,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Víkend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Událost"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spánek"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Zapnuto"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Vypnuto"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vypíná určité zvuky"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"V zařízení došlo k internímu problému. Dokud neprovedete obnovení továrních dat, může být nestabilní."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"V zařízení došlo k internímu problému. Další informace vám sdělí výrobce."</string> @@ -2346,8 +2344,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je zablokován"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nelze zrcadlit na displej"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použijte jiný kabel a zkuste to znovu"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Zařízení je moc zahřáté, a dokud se nezchladí, nemůže zrcadlit displej"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možná nepodporuje displeje"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Váš kabel USB-C se možná nedokáže správně připojit k displejům"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 751831a329c0..bedd941c6543 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1638,7 +1638,7 @@ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Cast skærm til enhed"</string> <string name="media_route_chooser_searching" msgid="6119673534251329535">"Søger efter enheder…"</string> <string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Indstillinger"</string> - <string name="media_route_controller_disconnect" msgid="7362617572732576959">"Afbryd forbindelsen"</string> + <string name="media_route_controller_disconnect" msgid="7362617572732576959">"Afbryd forbindelse"</string> <string name="media_route_status_scanning" msgid="8045156315309594482">"Søger..."</string> <string name="media_route_status_connecting" msgid="5845597961412010540">"Opretter forbindelse..."</string> <string name="media_route_status_available" msgid="1477537663492007608">"Tilgængelig"</string> @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan spore dine interaktioner med en app eller en hardwaresensor og interagere med apps på dine vegne."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Tillad"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Afvis"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Afinstaller"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"En app skjuler anmodningen om tilladelse, så dit svar kan ikke verificeres."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tryk på en funktion for at bruge den:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Vælg, hvilke funktioner du vil bruge med knappen til hjælpefunktioner"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Vælg de funktioner, du vil bruge via lydstyrkeknapperne"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Begivenhed"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sover"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Til"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Fra"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> slår nogle lyde fra"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Der er et internt problem med enheden, og den vil muligvis være ustabil, indtil du gendanner fabriksdataene."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Der er et internt problem med enheden. Kontakt producenten for at få yderligere oplysninger."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokeret"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det er ikke muligt at spejle til skærmen"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Brug et andet kabel, og prøv igen"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Din enhed er for varm og kan ikke spejle til skærmen, før den er kølet af"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablet understøtter muligvis ikke skærme"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dit USB-C-kabel kan muligvis ikke sluttes korrekt til skærmene"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 56816fbfd346..2867da922018 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Die Funktion kann deine Interaktionen mit einer App oder einem Hardwaresensor verfolgen und in deinem Namen mit Apps interagieren."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Zulassen"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Ablehnen"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Deinstallieren"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Eine App verdeckt die Berechtigungsanfrage und deine Antwort kann deshalb nicht überprüft werden."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Zum Auswählen der gewünschten Funktion tippen:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Funktionen auswählen, die du mit der Schaltfläche \"Bedienungshilfen\" verwenden möchtest"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Funktionen für Verknüpfung mit Lautstärketaste auswählen"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Wochenende"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Termin"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Schlafen"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"An"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Aus"</string> <string name="muted_by" msgid="91464083490094950">"Einige Töne werden von <xliff:g id="THIRD_PARTY">%1$s</xliff:g> stummgeschaltet"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Es liegt ein internes Problem mit deinem Gerät vor. Möglicherweise verhält es sich instabil, bis du es auf die Werkseinstellungen zurücksetzt."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Es liegt ein internes Problem mit deinem Gerät vor. Bitte wende dich diesbezüglich an den Hersteller."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon ist blockiert"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kann nicht auf das Display gespiegelt werden"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Verwende ein anderes Kabel und versuch es noch einmal"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Dein Gerät ist zu heiß und kann den Bildschirm erst spiegeln, wenn es abgekühlt ist"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel unterstützt eventuell keine Bildschirme"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dein USB-C-Kabel ist möglicherweise nicht zum Verbinden von Bildschirmen geeignet"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 7784e952ed3d..b6b4da2116c4 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Μπορεί να παρακολουθήσει τις αλληλεπιδράσεις σας με μια εφαρμογή ή έναν αισθητήρα εξοπλισμού και να αλληλεπιδράσει με εφαρμογές εκ μέρους σας."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Ναι"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Όχι"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Απεγκατάσταση"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Μια εφαρμογή αποκρύπτει το αίτημα άδειας, με αποτέλεσμα να μην είναι δυνατή η επαλήθευση της απάντησής σας."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Πατήστε μια λειτουργία για να ξεκινήσετε να τη χρησιμοποιείτε:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Επιλέξτε τις λειτουργίες που θέλετε να χρησιμοποιείτε με το κουμπί προσβασιμότητας."</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Επιλέξτε τις λειτουργίες που θέλετε να χρησιμοποιείτε με τη συντόμευση κουμπιού έντασης ήχου"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Σαββατοκύριακο"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Συμβάν"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Ύπνος"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Ενεργός"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Ανενεργός"</string> <string name="muted_by" msgid="91464083490094950">"Το τρίτο μέρος <xliff:g id="THIRD_PARTY">%1$s</xliff:g> θέτει ορισμένους ήχους σε σίγαση"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Υπάρχει ένα εσωτερικό πρόβλημα με τη συσκευή σας και ενδέχεται να είναι ασταθής μέχρι την επαναφορά των εργοστασιακών ρυθμίσεων."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Υπάρχει ένα εσωτερικό πρόβλημα με τη συσκευή σας. Επικοινωνήστε με τον κατασκευαστή σας για λεπτομέρειες."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Το μικρόφωνο έχει αποκλειστεί"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Δεν είναι δυνατός ο κατοπτρισμός στην οθόνη"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Χρησιμοποιήστε άλλο καλώδιο και δοκιμάστε ξανά"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Η θερμοκρασία της συσκευής σας είναι πολύ υψηλή και δεν είναι δυνατός ο κατοπτρισμός στην οθόνη μέχρι να μειωθεί"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Το καλώδιο μπορεί να μην υποστηρίζει οθόνες"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Το καλώδιο USB-C που έχετε ίσως να μην μπορεί να συνδεθεί σωστά σε οθόνες"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Διπλή οθόνη"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 72f907cd9484..94ae6cb493c8 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"It can track your interactions with an app or a hardware sensor, and interact with apps on your behalf."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstall"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"An app is obscuring the permission request so your response cannot be verified."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the Accessibility button"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Event"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sleeping"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"On"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"There\'s an internal problem with your device. Contact your manufacturer for details."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Your device is too warm and can\'t mirror to the display until it cools down"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 3381e97a06d8..ec41583fd00d 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"It can track your interactions with an app or a hardware sensor and interact with apps on your behalf."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstall"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"An app is obscuring the permission request so your response cannot be verified."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the accessibility button"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 6975a65451c0..2c155244cefe 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"It can track your interactions with an app or a hardware sensor, and interact with apps on your behalf."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstall"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"An app is obscuring the permission request so your response cannot be verified."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the Accessibility button"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Event"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sleeping"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"On"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"There\'s an internal problem with your device. Contact your manufacturer for details."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Your device is too warm and can\'t mirror to the display until it cools down"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 8f63abda4505..ff910802f0f8 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"It can track your interactions with an app or a hardware sensor, and interact with apps on your behalf."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstall"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"An app is obscuring the permission request so your response cannot be verified."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the Accessibility button"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Event"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sleeping"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"On"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"There\'s an internal problem with your device. Contact your manufacturer for details."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Your device is too warm and can\'t mirror to the display until it cools down"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index ac2d58c77efa..b1dd1f1438b5 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"It can track your interactions with an app or a hardware sensor, and interact with apps on your behalf."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstall"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"An app is obscuring the permission request so your response cannot be verified."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the accessibility button"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index cd83e421c92e..39a479e55b26 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Puede realizar el seguimiento de tus interacciones con una app o un sensor de hardware, así como interactuar con las apps por ti."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Rechazar"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Una app está cubriendo la solicitud de permiso, por lo que no se puede verificar tu respuesta."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Presiona una función para comenzar a usarla:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Selecciona las funciones a utilizar con el botón de accesibilidad"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Selecciona las funciones a usar con las teclas de volumen"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fin de semana"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Dormir"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activado"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desactivado"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> silencia algunos sonidos"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Existe un problema interno con el dispositivo, de modo que el dispositivo puede estar inestable hasta que restablezcas la configuración de fábrica."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Existe un problema interno con el dispositivo. Comunícate con el fabricante para obtener más información."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede duplicar la pantalla"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un cable diferente y vuelve a intentarlo"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"La temperatura del dispositivo es demasiado alta y no se puede duplicar la pantalla hasta que se enfríe"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Es posible que el cable no admita pantallas"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Es posible que el cable USB-C no se conecte a las pantallas de manera adecuada"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index bfd877eb443f..30ed6247d62f 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Puede registrar tus interacciones con una aplicación o un sensor de hardware, así como interactuar con las aplicaciones en tu nombre."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Denegar"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Una aplicación está ocultando la solicitud de permiso, por lo que no se puede verificar tu respuesta."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toca una función para empezar a usarla:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Selecciona qué funciones usar con el botón de accesibilidad"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Selecciona qué funciones usar con la tecla de volumen"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fin de semana"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Durmiendo"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activado"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desactivado"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> silencia algunos sonidos"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Se ha producido un problema interno en el dispositivo y es posible que este no sea estable hasta que restablezcas el estado de fábrica."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Se ha producido un problema interno en el dispositivo. Ponte en contacto con el fabricante para obtener más información."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede proyectar a la pantalla"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa otro cable y vuelve a intentarlo"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Tu dispositivo está demasiado caliente y no puede proyectar a la pantalla hasta que se enfríe"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"El cable puede no ser compatible con pantallas"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Puede que tu cable USB‑C no sea adecuado para conectarse a pantallas"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index dbb7663e0d0e..c4ea35188a8f 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"See saab jälgida teie suhtlust rakenduse või riistvaraanduriga ja teie eest rakendustega suhelda."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Luba"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Keela"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalli"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Rakendus varjab loataotlust, nii et teie vastust ei saa kinnitada."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Puudutage funktsiooni, et selle kasutamist alustada."</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Valige funktsioonid, mida juurdepääsetavuse nupuga kasutada"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Valige helitugevuse nupu otsetee funktsioonid"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Nädalavahetus"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Sündmus"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Magamine"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Sees"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Väljas"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vaigistab teatud helid"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Seadmes ilmnes sisemine probleem ja seade võib olla ebastabiilne seni, kuni lähtestate seadme tehase andmetele."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Seadmes ilmnes sisemine probleem. Üksikasjaliku teabe saamiseks võtke ühendust tootjaga."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon on blokeeritud"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ei saa ekraanile peegeldada"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Kasutage teist kaablit ja proovige uuesti"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Teie seade on liiga kuum ja ei saa ekraanile peegeldada enne, kui see jahtub"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kaabel ei pruugi ekraane toetada"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Teie USB-C-kaabel ei pruugi ekraanidega õigesti ühendust luua"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index a35236f9dd5c..26362d5ed812 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Aplikazioekin edo hardware-sentsoreekin dituzun interakzioen jarraipena egin dezake, eta zure izenean beste aplikazio batzuekin interakzioan jardun."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Baimendu"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Ukatu"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalatu"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikazio bat baimen-eskaera oztopatzen ari da eta, ondorioz, ezin da egiaztatu erantzuna."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Eginbide bat erabiltzen hasteko, saka ezazu:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Aukeratu zein eginbide erabili nahi duzun Erabilerraztasuna botoiarekin"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Aukeratu zein eginbide erabili nahi duzun bolumen-botoien lasterbidearekin"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Asteburua"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Gertaera"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Lo egiteko"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aktibatuta"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desaktibatuta"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> soinu batzuk isilarazten ari da"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Barneko arazo bat dago zure gailuan eta agian ezegonkor egongo da jatorrizko datuak berrezartzen dituzun arte."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Barneko arazo bat dago zure gailuan. Xehetasunak jakiteko, jarri fabrikatzailearekin harremanetan."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Blokeatuta dago mikrofonoa"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ezin da islatu pantailan"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Erabili beste kable bat eta saiatu berriro"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Gailua beroegi dago eta ezingo da pantailan islatu hoztu arte"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Baliteke kablea pantailekin bateragarria ez izatea"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Baliteke USB-C kablea behar bezala ez konektatzea pantailetara"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index c9df9d1fbd53..c96c5809e697 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"این عملکرد میتواند با برنامه یا حسگری سختافزاری تعاملاتتان را ردیابی کند و ازطرف شما با برنامهها تعامل داشته باشد."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"اجازه دادن"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"مجاز نبودن"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"حذف نصب"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"برنامهای درخواست اجازه را میپوشاند و بنابراین نمیتوان پاسخ شما را تأیید کرد."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"برای استفاده از ویژگی، روی آن ضربه بزنید:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"انتخاب ویژگیهای موردنظر برای استفاده با دکمه دسترسپذیری"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"انتخاب ویژگیهای موردنظر برای استفاده با میانبر کلید میزان صدا"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"آخر هفته"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"رویداد"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"خوابیدن"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"روشن"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"خاموش"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> درحال قطع کردن بعضی از صداهاست"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"دستگاهتان یک مشکل داخلی دارد، و ممکن است تا زمانی که بازنشانی دادههای کارخانه انجام نگیرد، بیثبات بماند."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"دستگاهتان یک مشکل داخلی دارد. برای جزئیات آن با سازندهتان تماس بگیرید."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"میکروفون مسدود شد"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"بازتاب دادن به نمایشگر ممکن نبود"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"از کابل دیگری استفاده کنید و دوباره امتحان کنید"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"دستگاه بسیار گرم است و تا زمانیکه خنک نشود نمیتواند محتوا را در نمایشگر بازتاب دهد."</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"شاید کابل از نمایشگر پشتیبانی نکند"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"کابل USB-C شما ممکن است بهدرستی به نمایشگرها وصل نشود"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 69a65a2e5c2a..c2037863fab1 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Se voi seurata toimintaasi sovelluksella tai laitteistoanturilla ja käyttää sovelluksia puolestasi."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Salli"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Estä"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Poista"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Sovellus peittää lupapyynnön, joten vastaustasi ei voi vahvistaa."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Aloita ominaisuuden käyttö napauttamalla sitä:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Valitse ominaisuudet, joita käytetään esteettömyyspainikkeella"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Valitse ominaisuudet, joita käytetään äänenvoimakkuuspikanäppäimellä"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Viikonloppuna"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Tapahtuma"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Nukkuminen"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Päällä"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Pois päältä"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> mykistää joitakin ääniä"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Laitteellasi on sisäinen ongelma, joka aiheuttaa epävakautta. Voit korjata tilanteen palauttamalla tehdasasetukset."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Laitteesi yhdistäminen ei onnistu sisäisen virheen takia. Saat lisätietoja valmistajalta."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni on estetty"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Näytön peilaaminen ei onnistu"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Käytä eri johtoa ja yritä uudelleen"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Laite on liian kuuma eikä voi peilata näyttöön, kunnes se viilenee"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Johto ei ehkä tue näyttöjä"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-johtosi ei ehkä yhdisty näyttöihin kunnolla"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Kaksoisnäyttö"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index acdfaab7ed01..f06d20f60f46 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Cette fonctionnalité peut faire le suivi de vos interactions avec une application ou un capteur matériel et interagir avec des applications en votre nom."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Autoriser"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Refuser"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Désinstaller"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Une application masque la demande d\'autorisation de sorte que votre réponse ne peut pas être vérifiée."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toucher une fonctionnalité pour commencer à l\'utiliser :"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choisir les fonctionnalités à utiliser à l\'aide du bouton d\'accessibilité"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choisir les fonctionnalités à utiliser avec le raccourci des touches de volume"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fin de semaine"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Événement"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sommeil"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activé"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Désactivé"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> désactive certains sons"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Un problème interne est survenu avec votre appareil. Il se peut qu\'il soit instable jusqu\'à ce que vous le réinitialisiez à ses paramètres par défaut."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Un problème interne est survenu avec votre appareil. Communiquez avec le fabricant pour obtenir plus de détails."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Le microphone est bloqué"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossible de dupliquer l\'écran"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un câble différent et réessayez"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Votre appareil est trop chaud et doit refroidir pour pouvoir dupliquer l\'écran"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble peut ne pas être compatible avec les écrans"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C peut ne pas se connecter correctement aux écrans"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index a96b17939908..b69db321c3f1 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Le service peut suivre vos interactions avec une application ou un capteur matériel, et interagir avec des applications de votre part."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Autoriser"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Refuser"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Désinstaller"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Une application masque la demande d\'autorisation. Votre réponse ne peut donc pas être vérifiée."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Appuyez sur une fonctionnalité pour commencer à l\'utiliser :"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choisir les fonctionnalités à utiliser avec le bouton Accessibilité"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choisir les fonctionnalités à utiliser avec le raccourci des touches de volume"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Week-end"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Événement"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sommeil"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activé"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Désactivé"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> coupe certains sons"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Un problème interne lié à votre appareil est survenu. Ce dernier risque d\'être instable jusqu\'à ce que vous rétablissiez la configuration d\'usine."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Un problème interne lié à votre appareil est survenu. Veuillez contacter le fabricant pour en savoir plus."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Le micro est bloqué"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Duplication impossible"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un autre câble et réessayez"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Votre appareil est trop chaud et ne peut pas se dupliquer sur l\'écran tant qu\'il n\'a pas refroidi."</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble n\'est peut-être pas compatible avec les écrans"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C n\'est peut-être pas connecté correctement à l\'écran"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index d001e90b23f7..f26dbfb9c941 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Pode facer un seguimento das túas interaccións cunha aplicación ou cun sensor de hardware, así como interactuar por ti coas aplicacións."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Denegar"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Hai unha aplicación que está ocultando a solicitude de permiso, polo que non se pode verificar a túa resposta."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tocar unha función para comezar a utilizala:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Escoller as funcións que queres utilizar co botón Accesibilidade"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Escolle as funcións que queres utilizar co atallo da tecla de volume"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fin de semana"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Durmindo"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activada"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desactivada"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando algúns sons"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Produciuse un erro interno no teu dispositivo e quizais funcione de maneira inestable ata o restablecemento dos datos de fábrica."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Produciuse un erro interno co teu dispositivo. Contacta co teu fabricante para obter máis información."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"O micrófono está bloqueado"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Non se pode proxectar contido na pantalla"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Cambia de cable e téntao de novo"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"O teu dispositivo está demasiado quente; mentres non arrefríe, non se poderá proxectar contido na pantalla"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Pode que o cable non sexa compatible con pantallas"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O teu cable USB-C pode que non se conecte ás pantallas de maneira adecuada"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 8b54db5f3946..d64e33a0fb5f 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"તે ઍપ અથવા હાર્ડવેર સેન્સર વડે તમારી ક્રિયાપ્રતિક્રિયાને ટ્રૅક કરી શકે છે અને તમારા વતી ઍપ સાથે ક્રિયાપ્રતિક્રિયા કરી શકે છે."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"મંજૂરી આપો"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"નકારો"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"અનઇન્સ્ટૉલ કરો"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"કોઈ ઍપ પરવાનગીની વિનંતીને ઢાંકી રહી છે, તેથી તમારા પ્રતિસાદની ચકાસણી કરી શકાતી નથી."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"સુવિધાનો ઉપયોગ શરૂ કરવા તેના પર ટૅપ કરો:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ઍક્સેસિબિલિટી બટન વડે તમે ઉપયોગમાં લેવા માગો છો તે સુવિધાઓ પસંદ કરો"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"વૉલ્યૂમ કી શૉર્ટકટ વડે તમે ઉપયોગમાં લેવા માગો છો તે સુવિધાઓ પસંદ કરો"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"સપ્તાહાંત"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ઇવેન્ટ"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"નિષ્ક્રિય"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ચાલુ છે"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"બંધ છે"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> અમુક અવાજોને મ્યૂટ કરે છે"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"તમારા ઉપકરણમાં આંતરિક સમસ્યા છે અને જ્યાં સુધી તમે ફેક્ટરી ડેટા ફરીથી સેટ કરશો નહીં ત્યાં સુધી તે અસ્થિર રહી શકે છે."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"તમારા ઉપકરણમાં આંતરિક સમસ્યા છે. વિગતો માટે તમારા નિર્માતાનો સંપર્ક કરો."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"માઇક્રોફોનને બ્લૉક કરવામાં આવ્યો છે"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ડિસ્પ્લે પર મિરર કરી શકાતું નથી"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"બીજા કોઈ કેબલનો ઉપયોગ કરો અને ફરી પ્રયાસ કરો"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"તમારું ડિવાઇસ ખૂબ જ ગરમ છે અને જ્યાં સુધી તે ઠંડું ન પડે ત્યાં સુધી મિરર કરી શકશે નહીં"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"શક્ય છે કે કેબલ કદાચ ડિસ્પ્લેને સપોર્ટ ન આપતો હોય"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"તમારો USB-C કેબલ કદાચ ડિસ્પ્લે સાથે યોગ્ય રીતે કનેક્ટ ન થાય"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 87313a5f0ae9..61db7849cecf 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"यह आपके और किसी ऐप्लिकेशन या हार्डवेयर सेंसर के बीच होने वाले इंटरैक्शन को ट्रैक कर सकता है और आपकी तरफ़ से ऐप्लिकेशन के साथ इंटरैक्ट कर सकता है."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"अनुमति दें"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"इंकार करें"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"अनइंस्टॉल करें"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ऐप्लिकेशन की वजह से, अनुमति का अनुरोध समझने में परेशानी हो रही है. इसलिए, आपके जवाब की पुष्टि नहीं की जा सकी."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"किसी सुविधा का इस्तेमाल करने के लिए, उस पर टैप करें:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"सुलभता बटन पर टैप करके, इस्तेमाल करने के लिए सुविधाएं चुनें"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"चुनें कि आवाज़ कम या ज़्यादा करने वाले बटन के शॉर्टकट से आप किन सुविधाओं का इस्तेमाल करना चाहते हैं"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"सप्ताहांत"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"इवेंट"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"सोते समय"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"चालू है"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"बंद है"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> कुछ आवाज़ें म्यूट कर रहा है"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"आपके डिवाइस में कोई अंदरूनी समस्या है और यह तब तक ठीक नहीं होगी जब तक आप फ़ैक्टरी डेटा रीसेट नहीं करते."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"आपके डिवाइस के साथ कोई आंतरिक गड़बड़ी हुई. विवरणों के लिए अपने निर्माता से संपर्क करें."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफ़ोन को ब्लॉक किया गया है"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिसप्ले का कॉन्टेंट नहीं दिखाया जा सकता"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"कोई दूसरा केबल इस्तेमाल करके फिर से कोशिश करें"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"आपका डिवाइस बहुत गर्म है. इसलिए, इसके ठंडा होने तक दूसरे डिसप्ले पर इसकी स्क्रीन शेयर नहीं की जा सकती"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ऐसा हो सकता है कि केबल, डिसप्ले के साथ ठीक से काम न करे"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ऐसा हो सकता है कि यूएसबी-सी केबल, डिसप्ले के साथ ठीक से कनेक्ट न हो पाए"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index c5e52bb9f8ad..4b9b7bcac02d 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Može pratiti vaše interakcije s aplikacijama ili senzorom uređaja i stupati u interakciju s aplikacijama u vaše ime."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Dopusti"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Odbij"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Deinstaliraj"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikacija prekriva upit za dopuštenje pa se vaš odgovor ne može potvrditi."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Dodirnite značajku da biste je počeli koristiti:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Odabir značajki za upotrebu pomoću gumba za Pristupačnost"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Odabir značajki za upotrebu pomoću prečaca tipki za glasnoću"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Vikend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Događaj"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spavanje"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Uključeno"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Isključeno"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvukove"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Na vašem uređaju postoji interni problem i možda neće biti stabilan dok ga ne vratite na tvorničko stanje."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Na vašem uređaju postoji interni problem. Obratite se proizvođaču za više pojedinosti."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Zrcaljenje na zaslon nije moguće"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabel i pokušajte ponovno"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Uređaj je previše zagrijan i ne može se zrcaliti na zaslon dok se ne ohladi"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možda ne podržava zaslone"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Vaš USB-C kabel možda nije ispravno povezan sa zaslonima"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dvostruki zaslon"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 39e49f67db64..5827300505b1 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Követheti az alkalmazásokkal és hardveres érzékelőkkel való interakcióit, és műveleteket végezhet az alkalmazásokkal az Ön nevében."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Engedélyezés"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Tiltás"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Eltávolítás"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Az egyik alkalmazás eltakarja az engedélykérelmet, így az Ön válasza nem ellenőrizhető."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Koppintson valamelyik funkcióra a használatához:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Kiválaszthatja a Kisegítő lehetőségek gombbal használni kívánt funkciókat"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Kiválaszthatja a hangerőgombbal használni kívánt funkciókat"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Hétvége"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Esemény"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Alvás"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Bekapcsolva"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Kikapcsolva"</string> <string name="muted_by" msgid="91464083490094950">"A(z) <xliff:g id="THIRD_PARTY">%1$s</xliff:g> lenémít néhány hangot"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Belső probléma van az eszközzel, és instabil lehet, amíg vissza nem állítja a gyári adatokat."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Belső probléma van az eszközzel. A részletekért vegye fel a kapcsolatot a gyártóval."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"A mikrofon le van tiltva"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nem lehet tükrözni a kijelzőre"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Használjon másik kábelt, és próbálja újra"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Az eszköz túl meleg – csak a lehűlése után tud tükrözni a kijelzőre."</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Előfordulhat, hogy a kábel nem támogatja a kijelzőket"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Előfordulhat, hogy az USB-C kábellel nem csatlakoztathatók megfelelően a kijelzők"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 75c7c747d9e3..9e313a6380d5 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -1908,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Շաբաթ-կիրակի"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Միջոցառում"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Քնի ժամանակ"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Միացված է"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Անջատված է"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>-ն անջատում է որոշ ձայներ"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Սարքում ներքին խնդիր է առաջացել և այն կարող է կրկնվել, մինչև չվերականգնեք գործարանային կարգավորումները:"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Սարքում ներքին խնդիր է առաջացել: Մանրամասների համար կապվեք արտադրողի հետ:"</string> @@ -2344,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Խոսափողն արգելափակված է"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Չհաջողվեց հայելապատճենել էկրանին"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Օգտագործեք այլ մալուխ և նորից փորձեք"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Ձեր սարքը գերտաքացել է և չի կարող հայելապատճենել էկրանը, մինչև ջերմաստիճանը չնվազի"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Մալուխը կարող է համատեղելի չլինել էկրանների հետ"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Հնարավոր է՝ USB-C մալուխը սխալ է միացված էկրանին"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 2d55f087600f..b4044aa3a0be 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Voice Access dapat melacak interaksi Anda dengan aplikasi atau sensor hardware, dan berinteraksi dengan aplikasi untuk Anda."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Izinkan"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Tolak"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Uninstal"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikasi menghalangi permintaan izin sehingga respons Anda tidak dapat diverifikasi."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Ketuk fitur untuk mulai menggunakannya:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Pilih fitur yang akan digunakan dengan tombol aksesibilitas"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Pilih fitur yang akan digunakan dengan pintasan tombol volume"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Akhir pekan"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Acara"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Tidur"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aktif"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Nonaktif"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> mematikan beberapa suara"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Ada masalah dengan perangkat. Hal ini mungkin membuat perangkat jadi tidak stabil dan perlu dikembalikan ke setelan pabrik."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Ada masalah dengan perangkat. Hubungi produsen perangkat untuk informasi selengkapnya."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon diblokir"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat mencerminkan ke layar"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan coba lagi"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Perangkat terlalu panas dan tidak dapat mencerminkan ke layar sampai cukup dingin"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak mendukung layar"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C mungkin tidak terhubung dengan benar ke layar"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index f5ad24b04053..062b4bca44a7 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Það getur fylgst með samskiptum þínum við forrit eða skynjara vélbúnaðar og haft samskipti við forrit fyrir þína hönd."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Leyfa"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Hafna"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Fjarlægja"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Forrit er að fela heimildarbeiðnina svo ekki er hægt að staðfesta svarið þitt."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Ýttu á eiginleika til að byrja að nota hann:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Veldu eiginleika sem á að nota með aðgengishnappinum"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Veldu eiginleika sem á að nota með flýtileið hljóðstyrkstakka"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Helgi"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Viðburður"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Svefn"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Kveikt"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Slökkt"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> þaggar í einhverjum hljóðum"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Innra vandamál kom upp í tækinu og það kann að vera óstöðugt þangað til þú núllstillir það."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Innra vandamál kom upp í tækinu. Hafðu samband við framleiðanda til að fá nánari upplýsingar."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Lokað er fyrir hljóðnemann"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekki er hægt að spegla á skjá"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Notaðu aðra snúru og reyndu aftur"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Tækið er of heitt og getur ekki speglað á skjáinn fyrr en það kólnar"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ekki er víst að snúran styðji skjái"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ekki er víst að USB-C-snúran tengist skjám á réttan hátt"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tveir skjáir"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 80262d2cc597..2e024cb06e6e 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Può tenere traccia delle tue interazioni con un\'app o un sensore hardware e interagire con app per tuo conto."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Consenti"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Rifiuta"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Disinstalla"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Un\'app nasconde la tua richiesta di autorizzazione, per cui non abbiamo potuto verificare la tua risposta."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tocca una funzionalità per iniziare a usarla:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Scegli le funzionalità da usare con il pulsante Accessibilità"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Scegli le funzionalità da usare con la scorciatoia dei tasti del volume"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fine settimana"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Notte"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"On"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> sta disattivando alcuni suoni"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Si è verificato un problema interno con il dispositivo, che potrebbe essere instabile fino al ripristino dei dati di fabbrica."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Si è verificato un problema interno con il dispositivo. Per informazioni dettagliate, contatta il produttore."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Microfono bloccato"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossibile eseguire il mirroring al display"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un altro cavo e riprova"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Il tuo dispositivo è troppo caldo e non può eseguire il mirroring al display finché non si raffredda"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Il cavo potrebbe non supportare i display"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Il cavo USB-C potrebbe non collegarsi correttamente ai display"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Doppio schermo"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 59dc3b27e8e1..e5e85c457859 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"אפשרות למעקב אחר האינטראקציה שלך עם אפליקציות או חיישני חומרה, וביצוע אינטראקציה בשמך."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"אישור"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"עדיף שלא"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"הסרה"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"אפליקציה מסתירה את בקשת ההרשאה כך שלא ניתן לאמת את התשובה שלך."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"יש להקיש על תכונה כדי להתחיל להשתמש בה:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"בחירת תכונה לשימוש עם לחצן הנגישות"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"בחירת תכונות לשימוש עם מקש הקיצור לעוצמת הקול"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"סוף השבוע"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"אירוע"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"שינה"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"מצב פעיל"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"מצב מושבת"</string> <string name="muted_by" msgid="91464083490094950">"חלק מהצלילים מושתקים על ידי <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"קיימת בעיה פנימית במכשיר שלך, וייתכן שהוא לא יתפקד כראוי עד שיבוצע איפוס לנתוני היצרן."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"קיימת בעיה פנימית במכשיר שלך. לקבלת פרטים, יש ליצור קשר עם היצרן."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"המיקרופון חסום"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"לא ניתן לשקף למסך"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"צריך להשתמש בכבל שונה ולנסות שוב"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"המכשיר שלך חם מדי. אי אפשר לשקף למסך עד שהמכשיר יתקרר"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"יכול להיות שהכבל לא תומך במסכים"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"יכול להיות שכבל ה-USB-C לא יתחבר למסכים כמו שצריך"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"מצב שני מסכים"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 2ac444bdaea9..6e86b57faadb 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"アプリやハードウェア センサーの操作を記録したり、自動的にアプリを操作したりできます。"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"許可"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"許可しない"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"アンインストール"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"権限のリクエストを遮っているアプリがあるため、同意の回答を確認できません。"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"使用を開始する機能をタップ:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ユーザー補助機能ボタンで使用する機能の選択"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"音量ボタンのショートカットで使用する機能の選択"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"週末"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"予定"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"睡眠中"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ON"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"OFF"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> により一部の音はミュートに設定"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"デバイスで内部的な問題が発生しました。データが初期化されるまで不安定になる可能性があります。"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"デバイスで内部的な問題が発生しました。詳しくはメーカーにお問い合わせください。"</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"マイクがブロックされています"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ディスプレイにミラーリングできません"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"別のケーブルでもう一度お試しください"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"デバイスが熱すぎるため、温度が下がるまでディスプレイにミラーリングできません"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ケーブルはディスプレイに対応していない可能性があります"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C ケーブルがディスプレイに正しく接続されていない可能性があります"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"デュアル スクリーン"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 8da54ce39653..34f56546e709 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"მას შეუძლია თვალი მიადევნოს თქვენს ინტერაქციებს აპის ან აპარატურის სენსორის საშუალებით, ასევე, თქვენი სახელით აწარმოოს აპებთან ინტერაქცია."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"დაშვება"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"უარყოფა"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"დეინსტალაცია"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"აპი მალავს ნებართვის მოთხოვნას, ასე რომ, თქვენი პასუხი ვერ დადასტურდება."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"შეეხეთ ფუნქციას მისი გამოყენების დასაწყებად:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"აირჩიეთ ფუნქციები, რომელთა გამოყენებაც გსურთ მარტივი წვდომის ღილაკით"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"აირჩიეთ ფუნქციები, რომელთა გამოყენებაც გსურთ ხმის ღილაკის მალსახმობით"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"შაბათ-კვირა"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"მოვლენა"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ძილისას"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ჩართული"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"გამორთული"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ზოგიერთ ხმას ადუმებს"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"ფიქსირდება თქვენი მ ოწყობილობის შიდა პრობლემა და შეიძლება არასტაბილური იყოს, სანამ ქარხნულ მონაცემების არ განაახლებთ."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"ფიქსირდება თქვენი მოწყობილობის შიდა პრობლემა. დეტალებისათვის, მიმართეთ თქვენს მწარმოებელს."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"მიკროფონი დაბლოკილია"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ეკრანზე არეკვლა შეუძლებელია"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"გამოიყენეთ სხვა კაბელი და ცადეთ ხელახლა"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"თქვენი მოწყობილობა ძალიან თბილია და ვერ ასახავს ეკრანზე სანამ არ გაგრილდება"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"კაბელს შეიძლება არ ჰქონდეს ეკრანების მხარდაჭერა"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"თქვენი USB-C კაბელი შეიძლება სათანადოდ არ უკავშირდებოდეს ეკრანებს"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"ორმაგი ეკრანი"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 935fa75c6291..839f3f9a643e 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Ол қолданбамен немесе жабдық датчигімен істеген тапсырмаларыңызды бақылайды және қолданбаларды сіздің атыңыздан пайдаланады."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Рұқсат ету"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Тыйым салу"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Жою"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Қолданба рұқсат сұрауын жасырып тұрғандықтан, жауабыңыз расталмайды."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Функцияны пайдалана бастау үшін түртіңіз:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"\"Арнайы мүмкіндіктер\" түймесімен қолданылатын функцияларды таңдаңыз"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Дыбыс деңгейі пернелері тіркесімімен қолданылатын функцияларды таңдаңыз"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Демалыс күндері"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Іс-шара"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Ұйқы режимі"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Қосулы"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Өшірулі"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> кейбір дыбыстарды өшіруде"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"There\'s an internal problem with your device. Contact your manufacturer for details."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон блокталған."</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дисплейге көшірмені көрсету мүмкін емес"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Басқа кабельмен әрекетті қайталап көріңіз."</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Құрылғыңыз тым қызып кетті, сондықтан ол суымайынша, дисплейге экран көшірмесін көрсете алмайды."</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерді қолдамауы мүмкін"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелі дисплейлерге дұрыс жалғанбаған болуы мүмкін."</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 95d6ca165628..9a29150f59ce 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"វាអាចតាមដានអន្តរកម្មរបស់អ្នកជាមួយនឹងកម្មវិធី ឬសេនស័រហាតវែរ និងធ្វើអន្តរកម្មជាមួយកម្មវិធីនានាជំនួសឱ្យអ្នក។"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"អនុញ្ញាត"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"បដិសេធ"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"លុប"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"កម្មវិធីមួយកំពុងបិទបាំងសំណើសុំការអនុញ្ញាត ដូច្នេះមិនអាចផ្ទៀងផ្ទាត់ការឆ្លើយតបរបស់អ្នកបានទេ។"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ចុចមុខងារណាមួយ ដើម្បចាប់ផ្ដើមប្រើ៖"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ជ្រើសរើសមុខងារ ដើម្បីប្រើជាមួយប៊ូតុងភាពងាយស្រួល"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ជ្រើសរើសមុខងារ ដើម្បីប្រើជាមួយផ្លូវកាត់គ្រាប់ចុចកម្រិតសំឡេង"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ចុងសប្ដាហ៍"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ព្រឹត្តិការណ៍"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"កំពុងដេក"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"បើក"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"បិទ"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> កំពុងបិទសំឡេងមួយចំនួន"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"មានបញ្ហាខាងក្នុងឧបករណ៍របស់អ្នក ហើយវាអ្នកមិនមានស្ថេរភាព រហូតទាល់តែអ្នកកំណត់ដូចដើមវិញទាំងស្រុង។"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"មានបញ្ហាខាងក្នុងឧបករណ៍របស់អ្នក ទំនាក់ទំនងក្រុមហ៊ុនផលិតឧបករណ៍របស់អ្នកសម្រាប់ព័ត៌មានបន្ថែម។"</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"មីក្រូហ្វូនត្រូវបានទប់ស្កាត់"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"មិនអាចបញ្ចាំងទៅផ្ទាំងអេក្រង់បានទេ"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ប្រើខ្សែផ្សេង រួចព្យាយាមម្តងទៀត"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ឧបករណ៍របស់អ្នកក្ដៅពេក និងមិនអាចបញ្ចាំងទៅផ្ទាំងអេក្រង់បានទេ រហូតទាល់តែវាចុះត្រជាក់សិន"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ខ្សែប្រហែលជាមិនអាចប្រើជាមួយផ្ទាំងអេក្រង់បានទេ"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ខ្សែ USB-C របស់អ្នកប្រហែលជាមិនអាចភ្ជាប់ផ្ទាំងអេក្រង់បានត្រឹមត្រូវទេ"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"អេក្រង់ពីរ"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index b6bef49256af..9f1700dff4b7 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ಇದು ಆ್ಯಪ್ ಅಥವಾ ಹಾರ್ಡ್ವೇರ್ ಸೆನ್ಸರ್ನ ಜೊತೆಗಿನ ನಿಮ್ಮ ಸಂವಹನಗಳನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡಬಹುದು, ಮತ್ತು ನಿಮ್ಮ ಪರವಾಗಿ ಆ್ಯಪ್ಗಳ ಜೊತೆ ಸಂವಹನ ನಡೆಸಬಹುದು."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ಅನುಮತಿಸಿ"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ನಿರಾಕರಿಸಿ"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"ಅನ್ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ಆ್ಯಪ್ವೊಂದು ಅನುಮತಿ ವಿನಂತಿಯನ್ನು ಮರೆಮಾಚುತ್ತಿರುವ ಕಾರಣ ನಿಮ್ಮ ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ವೈಶಿಷ್ಟ್ದ ಬಳಸುವುದನ್ನು ಪ್ರಾರಂಭಿಸಲು ಅದನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಬಟನ್ ಜೊತೆಗೆ ಬಳಸಲು ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ವಾಲ್ಯೂಮ್ ಕೀ ಶಾರ್ಟ್ಕಟ್ ಜೊತೆಗೆ ಬಳಸಲು ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ವಾರಾಂತ್ಯ"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ಈವೆಂಟ್"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ನಿದ್ರೆಯ ಸಮಯ"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ಆನ್ ಆಗಿದೆ"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ಆಫ್ ಆಗಿದೆ"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ಧ್ವನಿ ಮ್ಯೂಟ್ ಮಾಡುತ್ತಿದ್ದಾರೆ"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಆಂತರಿಕ ಸಮಸ್ಯೆಯಿದೆ ಹಾಗೂ ನೀವು ಫ್ಯಾಕ್ಟರಿ ಡೇಟಾವನ್ನು ರೀಸೆಟ್ ಮಾಡುವವರೆಗೂ ಅದು ಅಸ್ಥಿರವಾಗಬಹುದು."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಆಂತರಿಕ ಸಮಸ್ಯೆಯಿದೆ. ವಿವರಗಳಿಗಾಗಿ ನಿಮ್ಮ ತಯಾರಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string> @@ -2044,7 +2040,7 @@ <string name="autofill_update_title_with_type" msgid="5264152633488495704">"<xliff:g id="TYPE">%1$s</xliff:g> ಅನ್ನು "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>" ನಲ್ಲಿ ಅಪ್ಡೇಟ್ ಮಾಡಬೇಕೆ?"</string> <string name="autofill_update_title_with_2types" msgid="1797514386321086273">"<xliff:g id="TYPE_0">%1$s</xliff:g> ಮತ್ತು <xliff:g id="TYPE_1">%2$s</xliff:g> ಅನ್ನು "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>" ನಲ್ಲಿ ಅಪ್ಡೇಟ್ ಮಾಡಬೇಕೆ?"</string> <string name="autofill_update_title_with_3types" msgid="8285767070604652626">"ಈ ಮುಂದಿನ ಐಟಂಗಳನ್ನು "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ನಲ್ಲಿ ಅಪ್ಡೇಟ್ ಮಾಡಬೇಕೆ: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> ಮತ್ತು <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string> - <string name="autofill_save_yes" msgid="8035743017382012850">"ಉಳಿಸಿ"</string> + <string name="autofill_save_yes" msgid="8035743017382012850">"ಸೇವ್ ಮಾಡಿ"</string> <string name="autofill_save_no" msgid="9212826374207023544">"ಬೇಡ"</string> <string name="autofill_save_notnow" msgid="2853932672029024195">"ಸದ್ಯಕ್ಕೆ ಬೇಡ"</string> <string name="autofill_save_never" msgid="6821841919831402526">"ಎಂದೂ ಬೇಡ"</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ಡಿಸ್ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ಬೇರೆ ಕೇಬಲ್ ಬಳಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ನಿಮ್ಮ ಸಾಧನವು ತುಂಬಾ ಬಿಸಿಯಾಗಿದೆ ಮತ್ತು ಅದು ತಣ್ಣಗಾಗುವವರೆಗೆ ಡಿಸ್ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ಡಿಸ್ಪ್ಲೇಗಳನ್ನು ಕೇಬಲ್ ಬೆಂಬಲಿಸದಿರಬಹುದು"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ನಿಮ್ಮ USB-C ಕೇಬಲ್ ಡಿಸ್ಪ್ಲೇಗಳಿಗೆ ಸರಿಯಾಗಿ ಕನೆಕ್ಟ್ ಆಗದಿರಬಹುದು"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 612bc7111378..907e8025bfbc 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"앱 또는 하드웨어 센서와의 상호작용을 추적할 수 있으며 나를 대신해 앱과 상호작용할 수 있습니다."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"허용"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"거부"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"제거"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"앱에서 권한 요청을 가려서 응답을 확인할 수 없습니다."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"기능을 사용하려면 탭하세요"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"접근성 버튼으로 사용할 기능 선택"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"볼륨 키 단축키로 사용할 기능 선택"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"주말"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"캘린더 일정"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"수면 시간"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"사용"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"사용 중지"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>(이)가 일부 소리를 음소거함"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"사용 중인 기기 내부에 문제가 발생했습니다. 초기화할 때까지 불안정할 수 있습니다."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"사용 중인 기기 내부에 문제가 발생했습니다. 자세한 내용은 제조업체에 문의하세요."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"마이크가 차단됨"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"디스플레이에 미러링할 수 없음"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"다른 케이블을 사용하여 다시 시도해 보세요."</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"기기의 온도가 너무 높아서 온도가 내려갈 때까지 화면에 미러링할 수 없습니다."</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"디스플레이를 지원하지 않는 케이블일 수 있음"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"사용 중인 USB-C 케이블이 디스플레이에 제대로 연결되지 않을 수 있습니다."</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index ab92325140b8..5efcfc4bbf3c 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Кызмат колдонмодо жасаган аракеттериңизге же түзмөктүн сенсорлоруна көз салып, сиздин атыңыздан буйруктарды берет."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Ооба"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Жок"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Чыгарып салуу"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Колдонмо уруксат суроону жашырып койгондуктан, жообуңузду ырастоо мүмкүн эмес."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Функцияны колдонуп баштоо үчүн аны таптап коюңуз:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Атайын мүмкүнчүлүктөр баскычы менен колдонгуңуз келген функцияларды тандаңыз"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Үндү катуулатуу/акырындатуу баскычтары менен кайсы функцияларды иштеткиңиз келет?"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Дем алыш"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Иш-чара"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Уйку режими"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Күйүк"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Өчүк"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> айрым үндөрдү өчүрүүдө"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Түзмөгүңүздө ички көйгөй бар жана ал баштапкы абалга кайтарылмайынча туруктуу иштебей коюшу мүмкүн."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Түзмөгүңүздө ички көйгөй бар. Анын чоо-жайын билүү үчүн өндүрүүчүңүзгө кайрылыңыз."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон бөгөттөлгөн"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Экранга күзгүдөй чагылдыруу мүмкүн эмес"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Башка кабелди колдонуп, кайра аракет кылыңыз"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Түзмөгүңүз өтө ысып кетти жана ал муздамайынча башка экранга чыгара албайт"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерди колдоого албашы мүмкүн"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабели дисплейлерге туура туташпашы мүмкүн"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Кош экран"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index c4f74d04f834..d0f698d9864b 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ມັນສາມາດຕິດຕາມການໂຕ້ຕອບຂອງທ່ານກັບແອັບ ຫຼື ເຊັນເຊີຮາດແວໃດໜຶ່ງ ແລະ ໂຕ້ຕອບກັບແອັບໃນນາມຂອງທ່ານໄດ້."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ອະນຸຍາດ"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ປະຕິເສດ"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"ຖອນການຕິດຕັ້ງ"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ແອັບໜຶ່ງກຳລັງປິດບັງຄຳຮ້ອງຂໍການອະນຸຍາດ ດັ່ງນັ້ນຈຶ່ງບໍ່ສາມາດຢັ້ງຢືນຄຳຕອບຂອງທ່ານໄດ້."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ແຕະໃສ່ຄຸນສົມບັດໃດໜຶ່ງເພື່ອເລີ່ມການນຳໃຊ້ມັນ:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ເລືອກຄຸນສົມບັດເພື່ອໃຊ້ກັບປຸ່ມການຊ່ວຍເຂົ້າເຖິງ"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ເລືອກຄຸນສົມບັດເພື່ອໃຊ້ກັບທາງລັດປຸ່ມລະດັບສຽງ"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ທ້າຍອາທິດ"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ການນັດໝາຍ"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ການນອນ"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ເປີດຢູ່"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ປິດຢູ່"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ປິດສຽງບາງຢ່າງໄວ້"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"ມີບັນຫາພາຍໃນກັບອຸປະກອນຂອງທ່ານ, ແລະມັນອາດຈະບໍ່ສະຖຽນຈົນກວ່າທ່ານຕັ້ງເປັນຂໍ້ມູນໂຮງງານຄືນແລ້ວ."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"ມີບັນຫາພາຍໃນກັບອຸປະກອນຂອງທ່ານ. ຕິດຕໍ່ຜູ້ຜະລິດຂອງທ່ານສຳລັບລາຍລະອຽດຕ່າງໆ."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"ໄມໂຄຣໂຟນຖືກບລັອກໄວ້"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ບໍ່ສາມາດສະທ້ອນໄປຫາຈໍສະແດງຜົນໄດ້"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ກະລຸນາໃຊ້ສາຍອື່ນແລ້ວລອງໃໝ່"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ອຸປະກອນຂອງທ່ານຮ້ອນເກີນໄປ ແລະ ບໍ່ສາມາດສະທ້ອນໄປຫາຈໍສະແດງຜົນໄດ້ຈົນກວ່າມັນຈະເຢັນລົງ"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ສາຍອາດບໍ່ຮອງຮັບຈໍສະແດງຜົນ"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ສາຍ USB-C ຂອງທ່ານອາດບໍ່ໄດ້ເຊື່ອມຕໍ່ກັບຈໍສະແດງຜົນຢ່າງຖືກຕ້ອງ"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"ໜ້າຈໍຄູ່"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 96303beb19c9..6b6bc5099780 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1712,10 +1712,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Naudojant šią funkciją galima stebėti jūsų sąveiką su programa ar aparatinės įrangos jutikliu ir sąveikauti su programomis jūsų vardu."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Leisti"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Atmesti"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Pašalinti"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Programa užstoja leidimo užklausą, todėl negalima patvirtinti jūsų atsakymo."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Norėdami naudoti funkciją, palieskite ją:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Funkcijų, kurioms bus naudojamas pritaikomumo mygtukas, pasirinkimas"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Funkcijų, kurioms bus naudojamas garsumo spartusis klavišas, pasirinkimas"</string> @@ -1910,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Savaitgalį"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Įvykis"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Miegas"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Įjungti"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Išjungti"</string> <string name="muted_by" msgid="91464083490094950">"„<xliff:g id="THIRD_PARTY">%1$s</xliff:g>“ nutildo kai kuriuos garsus"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Iškilo vidinė su jūsų įrenginiu susijusi problema, todėl įrenginys gali veikti nestabiliai, kol neatkursite gamyklinių duomenų."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Iškilo vidinė su jūsų įrenginiu susijusi problema. Jei reikia išsamios informacijos, susisiekite su gamintoju."</string> @@ -2346,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonas užblokuotas"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Negalima bendrinti ekrano vaizdo ekrane"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Naudokite kitą laiką ir bandykite dar kartą"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Jūsų įrenginys per daug įkaitęs ir negali bendrinti ekrano vaizdo, kol atvės"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Laidas gali nepalaikyti ekranų"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Gali būti, kad USB-C laidu nepavyksta tinkamai prisijungti prie ekranų"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 1aa526ca31cd..931e681295d9 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Tā var izsekot jūsu mijiedarbību ar lietotni vai aparatūras sensoru un mijiedarboties ar lietotnēm jūsu vārdā."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Atļaut"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Neatļaut"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Atinstalēt"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Kāda lietotne padara atļaujas pieprasījumu nesaprotamu, tāpēc nevar verificēt jūsu atbildi."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Pieskarieties funkcijai, lai sāktu to izmantot"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Izvēlieties funkcijas, ko izmantot ar pieejamības pogu"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Izvēlieties funkcijas, ko izmantot ar skaļuma pogu saīsni"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Nedēļas nogalē"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Pasākums"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Gulēšana"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Ieslēgta"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Izslēgta"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> izslēdz noteiktas skaņas"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Jūsu ierīcē ir radusies iekšēja problēma, un ierīce var darboties nestabili. Lai to labotu, veiciet rūpnīcas datu atiestatīšanu."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Jūsu ierīcē ir radusies iekšēja problēma. Lai iegūtu plašāku informāciju, lūdzu, sazinieties ar ražotāju."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofons ir bloķēts."</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nevar spoguļot displeju"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Izmantojiet citu vadu un mēģiniet vēlreiz."</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Jūsu ierīce ir pārāk silta, un to nevar spoguļot displejā, kamēr tā nav atdzisusi."</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Iespējams, vads neatbalsta displejus"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Iespējams, jūsu USB-C vads nevarēs nodrošināt pareizu savienojumu ar displejiem."</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen režīms"</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 79c663d3f696..420adfe1ba65 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -1908,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Викенд"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Настан"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Спиење"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Вклучено"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Исклучено"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> исклучи некои звуци"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Настана внатрешен проблем со уредот и може да биде нестабилен сè додека не ресетирате на фабричките податоци."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Настана внатрешен проблем со уредот. Контактирајте го производителот за детали."</string> @@ -2344,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонот е блокиран"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се отсликува за прикажување"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Користете друг кабел и обидете се повторно"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Вашиот уред е премногу топол и не може да се отсликува на екранот додека не се излади"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелот можеби не поддржува екрани"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Кабелот USB-C можеби нема да се поврзе правилно со екраните"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index e281426d04af..466dab6b7165 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ഇതിന് ഒരു ആപ്പുമായോ ഹാർഡ്വെയർ സെൻസറുമായോ ഉള്ള നിങ്ങളുടെ ആശയവിനിമയങ്ങൾ ട്രാക്ക് ചെയ്യാനും നിങ്ങളുടെ പേരിൽ ആശയവിനിമയം നടത്താനും കഴിയും."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"അനുവദിക്കൂ"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"നിരസിക്കുക"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"അൺഇൻസ്റ്റാൾ ചെയ്യുക"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ഒരു ആപ്പ്, അനുമതി അഭ്യർത്ഥന മറയ്ക്കുന്നതിനാൽ നിങ്ങളുടെ പ്രതികരണം പരിശോധിച്ചുറപ്പിക്കാനാകില്ല."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ഉപയോഗിച്ച് തുടങ്ങാൻ ഫീച്ചർ ടാപ്പ് ചെയ്യുക:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ഉപയോഗസഹായി ബട്ടണിന്റെ സഹായത്തോടെ, ഉപയോഗിക്കാൻ ഫീച്ചറുകൾ തിരഞ്ഞെടുക്കുക"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"വോളിയം കീ കുറുക്കുവഴിയിലൂടെ ഉപയോഗിക്കാൻ ഫീച്ചറുകൾ തിരഞ്ഞെടുക്കുക"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"വാരാന്ത്യം"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ഇവന്റ്"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ഉറക്കം"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ഓണാണ്"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ഓഫാണ്"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ചില ശബ്ദങ്ങൾ മ്യൂട്ട് ചെയ്യുന്നു"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"നിങ്ങളുടെ ഉപകരണത്തിൽ ഒരു ആന്തരിക പ്രശ്നമുണ്ട്, ഫാക്ടറി വിവര പുനഃസജ്ജീകരണം ചെയ്യുന്നതുവരെ ഇതു അസ്ഥിരമായിരിക്കാനിടയുണ്ട്."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"നിങ്ങളുടെ ഉപകരണത്തിൽ ഒരു ആന്തരിക പ്രശ്നമുണ്ട്. വിശദാംശങ്ങൾക്കായി നിർമ്മാതാവിനെ ബന്ധപ്പെടുക."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"മൈക്രോഫോൺ ബ്ലോക്ക് ചെയ്തു"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ഡിസ്പ്ലേയിലേക്ക് മിറർ ചെയ്യാനാകില്ല"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"മറ്റൊരു കേബിൾ ഉപയോഗിച്ച് വീണ്ടും ശ്രമിക്കുക"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"നിങ്ങളുടെ ഉപകരണത്തിന് ചൂട് വളരെ കൂടുതലാണ്, അത് തണുക്കുന്നത് വരെ ഡിസ്പ്ലേയിലേക്ക് മിറർ ചെയ്യാനാകില്ല"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"കേബിൾ, ഡിസ്പ്ലേകളെ പിന്തുണച്ചേക്കില്ല"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"നിങ്ങളുടെ USB-C കേബിൾ, ഡിസ്പ്ലേകളിലേക്ക് ശരിയായി കണക്റ്റ് ആയേക്കില്ല"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"ഡ്യുവൽ സ്ക്രീൻ"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index 6615ac7457dc..3cb9cb28636f 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Энэ нь таны апп болон техник хангамжийн мэдрэгчтэй хийх харилцан үйлдлийг хянах болон таны өмнөөс апптай харилцан үйлдэл хийх боломжтой."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Зөвшөөрөх"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Татгалзах"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Устгах"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Апп зөвшөөрлийн хүсэлтийг хааж байгаа тул таны хариултыг баталгаажуулах боломжгүй."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Үүнийг ашиглаж эхлэхийн тулд онцлог дээр товшино уу:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Хандалтын товчлуурын тусламжтай ашиглах онцлогуудыг сонгоно уу"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Дууны түвшний түлхүүрийн товчлолын тусламжтай ашиглах онцлогуудыг сонгоно уу"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Амралтын өдөр"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Үйл явдал"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Унтлагын цаг"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Асаалттай"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Унтраалттай"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> зарим дууны дууг хааж байна"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Таны төхөөрөмжид дотоод алдаа байна.Та төхөөрөмжөө үйлдвэрээс гарсан төлөвт шилжүүлэх хүртэл таны төхөөрөмж чинь тогтворгүй байж болох юм."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Таны төхөөрөмжид дотоод алдаа байна. Дэлгэрэнгүй мэдээлэл авахыг хүсвэл үйлдвэрлэгчтэйгээ холбоо барина уу."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофоныг блоклосон байна"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дэлгэцэд тусгал үүсгэх боломжгүй"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Өөр кабель ашиглаад, дахин оролдоно уу"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Таны төхөөрөмж хэт халсан бөгөөд үүнийг хөрөх хүртэл дэлгэцэд тусгал үүсгэх боломжгүй"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель нь дэлгэцүүдийг дэмждэггүй байж магадгүй"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Таны USB-C кабель дэлгэцүүдэд зохих ёсоор холбогдохгүй байж магадгүй"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 6bfa0bfbf328..b20754b6fbb1 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"हे तुमचा ॲप किंवा हार्डवेअर सेन्सरसोबतचा परस्परसंवाद ट्रॅक करू शकते आणि इतर ॲप्ससोबत तुमच्या वतीने संवाद साधू शकते."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"अनुमती द्या"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"नकार द्या"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"अनइंस्टॉल करा"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"परवानगी मागणारी विनंती अॅपमुळे अस्पष्ट होत असल्याने, तुमच्या प्रतिसादाची पडताळणी केली जाऊ शकत नाही."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"वैशिष्ट्य वापरणे सुरू करण्यासाठी त्यावर टॅप करा:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"अॅक्सेसिबिलिटी बटणासोबत वापरायची असलेली ॲप्स निवडा"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"व्हॉल्यूम की शॉर्टकटसोबत वापरायची असलेली ॲप्स निवडा"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"आठवड्याच्या शेवटी"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"इव्हेंट"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"झोपताना"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"सुरू आहे"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"बंद आहे"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> काही ध्वनी म्यूट करत आहे"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"आपल्या डिव्हाइसमध्ये अंतर्गत समस्या आहे आणि तुमचा फॅक्टरी डेटा रीसेट होईपर्यंत ती अस्थिर असू शकते."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"आपल्या डिव्हाइसमध्ये अंतर्गत समस्या आहे. तपशीलांसाठी आपल्या निर्मात्याशी संपर्क साधा."</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 769f0bddf55d..6b1742ce01e0 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Ciri ini boleh menjejaki interaksi anda dengan apl atau penderia perkakasan dan berinteraksi dengan apl bagi pihak anda."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Benarkan"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Tolak"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Nyahpasang"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Apl menghalang permintaan kebenaran, maka jawapan anda tidak dapat disahkan."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Ketik ciri untuk mula menggunakan ciri itu:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Pilih ciri untuk digunakan dengan butang kebolehaksesan"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Pilih ciri untuk digunakan dengan pintasan kekunci kelantangan"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Hujung minggu"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Acara"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Tidur"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Hidup"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Mati"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> meredamkan sesetengah bunyi"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Terdapat masalah dalaman dengan peranti anda. Peranti mungkin tidak stabil sehingga anda membuat tetapan semula data kilang."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Terdapat masalah dalaman dengan peranti anda. Hubungi pengilang untuk mengetahui butirannya."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon disekat"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat menyegerakkan kepada paparan"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan cuba lagi"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Peranti anda terlalu panas dan tidak dapat dicerminkan kepada paparan sehingga peranti sejuk"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak menyokong paparan"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C anda mungkin tidak bersambung kepada paparan dengan betul"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dwiskrin"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index df94876358ed..edbe31f078d8 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -1908,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"စနေ၊ တနင်္ဂနွေ"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"အစီအစဉ်"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"အိပ်နေချိန်"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ဖွင့်"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ပိတ်"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> သည် အချို့အသံကို ပိတ်နေသည်"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"သင့်ကိရိယာအတွင်းပိုင်းတွင် ပြဿနာရှိနေပြီး၊ မူလစက်ရုံထုတ်အခြေအနေအဖြစ် ပြန်လည်ရယူနိုင်သည်အထိ အခြေအနေမတည်ငြိမ်နိုင်ပါ။"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"သင့်ကိရိယာအတွင်းပိုင်းတွင် ပြဿနာရှိနေ၏။ အသေးစိတ်သိရန်အတွက် ပစ္စည်းထုတ်လုပ်သူအား ဆက်သွယ်ပါ။"</string> @@ -2344,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"မိုက်ခရိုဖုန်း ပိတ်ထားသည်"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ဖန်သားပြင်တွင် စကရင်ပွား၍ မရပါ"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"အခြားကေဘယ်ကြိုးသုံးပြီး ထပ်စမ်းကြည့်ပါ"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"သင့်စက်ပစ္စည်း ပူလွန်းနေသဖြင့် ၎င်းမအေးသေးမီ ဖန်သားပြင်သို့ စကရင်ပွား၍မရပါ"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ကေဘယ်ကြိုးက ဖန်သားပြင်များကို မပံ့ပိုးခြင်း ဖြစ်နိုင်သည်"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"သင့် USB-C ကေဘယ်ကြိုးသည် ဖန်သားပြင်များနှင့် မှန်ကန်စွာ ချိတ်ဆက်မထားခြင်း ဖြစ်နိုင်သည်"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 4aca6fc6fa64..0732e67b4235 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan spore kommunikasjonen din med en app eller maskinvaresensor og kommunisere med apper på dine vegne."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Tillat"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Avvis"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Avinstaller"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"En app dekker forespørselen om tillatelse, så svaret ditt kan ikke bekreftes."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Trykk på en funksjon for å begynne å bruke den:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Velg funksjonene du vil bruke med Tilgjengelighet-knappen"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Velg funksjonene du vil bruke med volumtastsnarveien"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Helg"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Aktivitet"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sover"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"På"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Av"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> slår av noen lyder"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Det har oppstått et internt problem på enheten din, og den kan være ustabil til du tilbakestiller den til fabrikkdata."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Det har oppstått et internt problem på enheten din. Ta kontakt med produsenten for mer informasjon."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokkert"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan ikke speile til skjermen"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Bruk en annen kabel og prøv igjen"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Enheten er for varm og kan ikke speiles til skjermen før den kjøles ned"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabelen støtter kanskje ikke skjermer"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-kabelen din kobler seg kanskje ikke til skjermer på riktig måte"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 16279129f730..7ac4c14d3622 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"यसले कुनै एप वा हार्डवेयर सेन्सरसँग तपाईंले गर्ने अन्तर्क्रिया ट्र्याक गर्न सक्छ र तपाईंका तर्फबाट एपहरूसँग अन्तर्क्रिया गर्न सक्छ।"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"अनुमति दिनुहोस्"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"नदिनुहोस्"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"अनइन्स्टल गर्नुहोस्"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"कुनै एपका कारण अनुमतिसम्बन्धी अनुरोध बुझ्न कठिनाइ भइरहेकाले तपाईंको जवाफको पुष्टि गर्न सकिएन।"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"कुनै सुविधा प्रयोग गर्न थाल्न उक्त सुविधामा ट्याप गर्नुहोस्:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"पहुँचको बटनमार्फत प्रयोग गर्न चाहेका सुविधाहरू छनौट गर्नुहोस्"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"भोल्युम कुञ्जीको सर्टकटमार्फत प्रयोग गर्न चाहेका सुविधाहरू छनौट गर्नुहोस्"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"शनिबार"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"कार्यक्रम"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"निदाएका बेला"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"अन छ"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"अफ छ"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ले केही ध्वनिहरू म्युट गर्दै छ"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"तपाईंको यन्त्रसँग आन्तरिक समस्या छ, र तपाईंले फ्याक्ट्री डाटा रिसेट नगर्दासम्म यो अस्थिर रहन्छ।"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"तपाईंको यन्त्रसँग आन्तरिक समस्या छ। विवरणहरूको लागि आफ्नो निर्मातासँग सम्पर्क गर्नुहोस्।"</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफोन म्युट गरिएको छ"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेमा मिरर गर्न सकिएन"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"अर्कै केबल प्रयोग गरी फेरि प्रयास गर्नुहोस्"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"तपाईंको डिभाइस निकै तातो छ र यो चिसो नभएसम्म यसले डिस्प्लेमा मिरर गर्न सक्दैन"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"यो केबल डिस्प्लेहरूमा प्रयोग गर्न नमिल्न सक्छ"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तपाईंको USB-C केबल डिस्प्लेहरूमा राम्रोसँग नजोडिन सक्छ"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 5ae7d6d25372..8f086895dd7d 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Deze functie kan je interacties met een app of een hardwaresensor bijhouden en namens jou met apps communiceren."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Toestaan"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Weigeren"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Verwijderen"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Een app dekt het verzoek om rechten af, waardoor je reactie niet kan worden geverifieerd."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tik op een functie om deze te gebruiken:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Functies kiezen voor gebruik met de knop Toegankelijkheid"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Functies kiezen voor gebruik met de sneltoets via de volumeknop"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Afspraken"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Slapen"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aan"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Uit"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> zet sommige geluiden uit"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Er is een intern probleem met je apparaat. Het apparaat kan instabiel zijn totdat u het apparaat terugzet naar de fabrieksinstellingen."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Er is een intern probleem met je apparaat. Neem contact op met de fabrikant voor meer informatie."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Microfoon is geblokkeerd"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan niet spiegelen naar scherm"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik een andere kabel en probeer het opnieuw"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Je apparaat is te warm en kan pas naar het scherm mirroren als het is afgekoeld"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"De kabel ondersteunt misschien geen schermen"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Je USB-C-kabel sluit misschien niet goed aan op schermen"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 9997f165bde6..e91e005f095b 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ଏହା କୌଣସି ଆପ କିମ୍ବା ହାର୍ଡୱେର ସେନ୍ସର ସହ ଆପଣଙ୍କର ଇଣ୍ଟେରାକ୍ସନକୁ ଟ୍ରାକ କରିପାରେ ଏବଂ ଆପଣଙ୍କ ତରଫରୁ ଆପ୍ସ ସହ ଇଣ୍ଟରାକ୍ଟ କରିପାରେ।"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ଅନୁମତି"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ଅଗ୍ରାହ୍ୟ"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"ଅନଇନଷ୍ଟଲ କରନ୍ତୁ"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ଏକ ଆପ ଅନୁମତି ଅନୁରୋଧକୁ ଅସ୍ପଷ୍ଟ କରୁଛି ତେଣୁ ଆପଣଙ୍କ ଉତ୍ତରକୁ ଯାଞ୍ଚ କରାଯାଇପାରିବ ନାହିଁ।"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ଏକ ଫିଚର୍ ବ୍ୟବହାର କରିବା ଆରମ୍ଭ କରିବାକୁ ଏହାକୁ ଟାପ୍ କରନ୍ତୁ:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ଆକ୍ସେସିବିଲିଟୀ ବଟନ୍ ସହିତ ବ୍ୟବହାର କରିବାକୁ ଫିଚରଗୁଡ଼ିକ ବାଛନ୍ତୁ"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ଭଲ୍ୟୁମ୍ କୀ ସର୍ଟକଟ୍ ସହିତ ବ୍ୟବହାର କରିବାକୁ ଫିଚରଗୁଡ଼ିକ ବାଛନ୍ତୁ"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ସପ୍ତାହାନ୍ତ"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ଇଭେଣ୍ଟ"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ଶୋଇବା"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ଚାଲୁ ଅଛି"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ବନ୍ଦ ଅଛି"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> କିଛି ସାଉଣ୍ଡକୁ ମ୍ୟୁଟ୍ କରୁଛି"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"ଆପଣଙ୍କ ଡିଭାଇସ୍ରେ ଏକ ସମସ୍ୟା ରହିଛି ଏବଂ ଆପଣ ଫ୍ୟାକ୍ଟୋରୀ ଡାଟା ରିସେଟ୍ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହା ଅସ୍ଥିର ରହିପାରେ।"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"ଆପଣଙ୍କ ଡିଭାଇସରେ ଏକ ସମସ୍ୟା ରହିଛି। ବିବରଣୀ ପାଇଁ ଆପଣଙ୍କ ଉତ୍ପାଦକଙ୍କ ସହ କଣ୍ଟାକ୍ଟ କରନ୍ତୁ।"</string> @@ -1951,7 +1947,7 @@ <string name="notification_history_title_placeholder" msgid="7748630986182249599">"କଷ୍ଟମ୍ ଆପ୍ ବିଜ୍ଞପ୍ତି"</string> <string name="user_creation_account_exists" msgid="2239146360099708035">"<xliff:g id="APP">%1$s</xliff:g>ରେ ଏକ ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରିବା ପାଇଁ <xliff:g id="ACCOUNT">%2$s</xliff:g>କୁ (ପୂର୍ବରୁ ଏହି ଆକାଉଣ୍ଟ ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ନାମରେ ଅଛି) ଅନୁମତି ଦେବେ?"</string> <string name="user_creation_adding" msgid="7305185499667958364">"<xliff:g id="APP">%1$s</xliff:g>ରେ ଏକ ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରିବା ପାଇଁ <xliff:g id="ACCOUNT">%2$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string> - <string name="supervised_user_creation_label" msgid="6884904353827427515">"ନିରୀକ୍ଷିତ ଉପଯୋଗକର୍ତ୍ତା ଯୋଗ କରନ୍ତୁ"</string> + <string name="supervised_user_creation_label" msgid="6884904353827427515">"ନିରୀକ୍ଷିତ ୟୁଜର ଯୋଗ କରନ୍ତୁ"</string> <string name="language_selection_title" msgid="52674936078683285">"ଏକ ଭାଷା ଯୋଗ କରନ୍ତୁ"</string> <string name="country_selection_title" msgid="5221495687299014379">"ପସନ୍ଦର ଅଞ୍ଚଳ"</string> <string name="search_language_hint" msgid="7004225294308793583">"ଭାଷାର ନାମ ଟାଇପ୍ କରନ୍ତୁ"</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"ମାଇକ୍ରୋଫୋନକୁ ବ୍ଲକ କରାଯାଇଛି"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ଡିସପ୍ଲେ କରିବାକୁ ମିରର କରାଯାଇପାରିବ ନାହିଁ"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ଏକ ଭିନ୍ନ କେବୁଲ ବ୍ୟବହାର କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ଆପଣଙ୍କ ଡିଭାଇସ ବହୁତ ଗରମ ଅଛି ଏବଂ ଏହା ଥଣ୍ଡା ନହେବା ପର୍ଯ୍ୟନ୍ତ ଡିସପ୍ଲେକୁ ମିରର କରିପାରିବ ନାହିଁ"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକୁ ସମର୍ଥନ କରିନପାରେ"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ଆପଣଙ୍କ USB-C କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକ ସହ ସଠିକ ଭାବରେ କନେକ୍ଟ ହୋଇନପାରେ"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 4b9289dfd457..29a0094ccdaa 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ਇਹ ਕਿਸੇ ਐਪ ਜਾਂ ਹਾਰਡਵੇਅਰ ਸੈਂਸਰ ਦੇ ਨਾਲ ਤੁਹਾਡੀਆਂ ਅੰਤਰਕਿਰਿਆਵਾਂ ਨੂੰ ਟਰੈਕ ਕਰ ਸਕਦੀ ਹੈ, ਅਤੇ ਤੁਹਾਡੀ ਤਰਫ਼ੋਂ ਐਪਾਂ ਦੇ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰ ਸਕਦੀ ਹੈ।"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"ਕਰਨ ਦਿਓ"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ਨਾ ਕਰਨ ਦਿਓ"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"ਅਣਸਥਾਪਤ ਕਰੋ"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ਕੋਈ ਐਪ ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਬੇਨਤੀ ਨੂੰ ਅਸਪਸ਼ਟ ਕਰ ਰਹੀ ਹੈ, ਇਸ ਲਈ ਤੁਹਾਡੇ ਜਵਾਬ ਦੀ ਪੁਸ਼ਟੀ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ਕਿਸੇ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਵਰਤਣਾ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਉਸ \'ਤੇ ਟੈਪ ਕਰੋ:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"ਪਹੁੰਚਯੋਗਤਾ ਬਟਨ ਨਾਲ ਵਰਤਣ ਲਈ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਚੁਣੋ"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ਅਵਾਜ਼ ਕੁੰਜੀ ਸ਼ਾਰਟਕੱਟ ਨਾਲ ਵਰਤਣ ਲਈ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਚੁਣੋ"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ਹਫ਼ਤੇ ਦਾ ਅੰਤਲਾ ਦਿਨ"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ਇਵੈਂਟ"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"ਸੌਣ ਵੇਲੇ"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ਚਾਲੂ ਹੈ"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ਬੰਦ ਹੈ"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ਕੁਝ ਧੁਨੀਆਂ ਨੂੰ ਮਿਊਟ ਕਰ ਰਹੀ ਹੈ"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨਾਲ ਇੱਕ ਅੰਦਰੂਨੀ ਸਮੱਸਿਆ ਹੈ ਅਤੇ ਇਹ ਅਸਥਿਰ ਹੋ ਸਕਦੀ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਨਹੀਂ ਕਰਦੇ।"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨਾਲ ਇੱਕ ਅੰਦਰੂਨੀ ਸਮੱਸਿਆ ਸੀ। ਵੇਰਵਿਆਂ ਲਈ ਆਪਣੇ ਨਿਰਮਾਤਾ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।"</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ਕੋਈ ਵੱਖਰੀ ਕੇਬਲ ਵਰਤ ਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਰਮ ਹੈ ਅਤੇ ਜਦੋਂ ਤੱਕ ਇਹ ਠੰਡਾ ਨਹੀਂ ਹੋ ਜਾਂਦਾ ਉਦੋਂ ਤੱਕ ਡਿਸਪਲੇ ਨੂੰ ਪ੍ਰਤਿਬਿੰਬਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਦਾ ਸਮਰਥਨ ਨਾ ਕਰੇ"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਤੁਹਾਡੀ USB-C ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਨਾਲ ਠੀਕ ਤਰ੍ਹਾਂ ਕਨੈਕਟ ਨਾ ਹੋਵੇ"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 0273cc34a175..208810d8ea61 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1712,10 +1712,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Może śledzić Twoje interakcje z aplikacjami lub czujnikiem sprzętowym, a także obsługiwać aplikacje za Ciebie."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Zezwól"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Odmów"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Odinstaluj"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikacja zasłania prośbę o uprawnienia, więc nie można zweryfikować Twojej odpowiedzi."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Wybierz funkcję, aby zacząć z niej korzystać:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Wybierz funkcje, których chcesz używać z przyciskiem ułatwień dostępu"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Wybierz funkcje, do których chcesz używać skrótu z klawiszami głośności"</string> @@ -1910,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Wydarzenie"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Sen"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Włączono"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Wyłączono"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> wycisza niektóre dźwięki"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"W Twoim urządzeniu wystąpił problem wewnętrzny. Może być ono niestabilne, dopóki nie przywrócisz danych fabrycznych."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"W Twoim urządzeniu wystąpił problem wewnętrzny. Skontaktuj się z jego producentem, by otrzymać szczegółowe informacje."</string> @@ -2346,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon jest zablokowany"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nie można utworzyć odbicia lustrzanego na wyświetlaczu"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Użyj innego kabla i spróbuj ponownie"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Urządzenie ma zbyt wysoką temperaturę i nie może utworzyć odbicia lustrzanego zawartości ekranu, dopóki się nie ochłodzi."</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel może nie obsługiwać wyświetlaczy"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C może nie łączyć się prawidłowo z wyświetlaczami"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Podwójny ekran"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index ec7658da99bf..c509ed59c742 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Pode monitorar suas interações com um app ou um sensor de hardware e interagir com apps em seu nome."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Negar"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Um app está ocultando a solicitação de permissão e impedindo a verificação da sua resposta."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toque em um recurso para começar a usá-lo:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Escolha recursos para usar com o botão de acessibilidade"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Escolha recursos para usar com o atalho da tecla de volume"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fim de semana"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Dormir"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Ativada"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desativada"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando alguns sons"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Há um problema interno com seu dispositivo. Ele pode ficar instável até que você faça a redefinição para configuração original."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Há um problema interno com seu dispositivo. Entre em contato com o fabricante para saber mais detalhes."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"O dispositivo está muito quente. Não será possível espelhar a tela até ele resfriar"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index b834c0f1f48d..daa2504c71ce 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Pode monitorizar as suas interações com uma app ou um sensor de hardware e interagir com apps em seu nome."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Recusar"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Uma app está a ocultar o pedido de autorização e, por isso, não é possível validar a sua resposta."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toque numa funcionalidade para começar a utilizá-la:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Escolha funcionalidades para utilizar com o botão Acessibilidade"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Escolha funcionalidades para usar com o atalho das teclas de volume"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fim de semana"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"A dormir"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Ativada"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desativada"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está a desativar alguns sons."</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Existe um problema interno no seu dispositivo e pode ficar instável até efetuar uma reposição de dados de fábrica."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Existe um problema interno no seu dispositivo. Contacte o fabricante para obter mais informações."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar para o ecrã"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use um cabo diferente e tente novamente"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"O dispositivo está demasiado quente e não consegue espelhar para o ecrã até arrefecer"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"O cabo pode não suportar ecrãs"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O cabo USB-C pode não se ligar a ecrãs corretamente"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index ec7658da99bf..c509ed59c742 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Pode monitorar suas interações com um app ou um sensor de hardware e interagir com apps em seu nome."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Negar"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Desinstalar"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Um app está ocultando a solicitação de permissão e impedindo a verificação da sua resposta."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toque em um recurso para começar a usá-lo:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Escolha recursos para usar com o botão de acessibilidade"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Escolha recursos para usar com o atalho da tecla de volume"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fim de semana"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Evento"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Dormir"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Ativada"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desativada"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando alguns sons"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Há um problema interno com seu dispositivo. Ele pode ficar instável até que você faça a redefinição para configuração original."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Há um problema interno com seu dispositivo. Entre em contato com o fabricante para saber mais detalhes."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"O dispositivo está muito quente. Não será possível espelhar a tela até ele resfriar"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index dc89003efa3d..562fcc90f35e 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Poate să urmărească interacțiunile tale cu o aplicație sau cu un senzor hardware și să interacționeze cu aplicații în numele tău."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permite"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Refuz"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Dezinstalează"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"O aplicație blochează solicitarea de permisiune, așa că răspunsul nu se poate verifica."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Atinge o funcție ca să începi să o folosești:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Alege funcțiile pe care să le folosești cu butonul de accesibilitate"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Alege funcțiile pentru comanda rapidă a butonului de volum"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Eveniment"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Somn"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Activată"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Dezactivată"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> dezactivează anumite sunete"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"A apărut o problemă internă pe dispozitiv, iar acesta poate fi instabil până la revenirea la setările din fabrică."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"A apărut o problemă internă pe dispozitiv. Pentru detalii, contactează producătorul."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Microfonul este blocat"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nu se poate oglindi pe ecran"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Folosește alt cablu și încearcă din nou"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Dispozitivul este prea cald și nu poate oglindi ecranul până când nu se răcește"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cablul poate să nu fie compatibil cu ecranele"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Cablul USB-C poate să nu se conecteze corespunzător la ecrane"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 8bd65763bbae..84a47760008c 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1712,10 +1712,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Сервис может отслеживать ваше взаимодействие с приложениями и датчиками устройства и давать приложениям команды от вашего имени."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Разрешить"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Отклонить"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Удалить"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Невозможно принять ваш ответ, поскольку запрос разрешения скрыт другим приложением."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Выберите, какую функцию использовать:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Выберите функции, которые будут запускаться с помощью кнопки специальных возможностей"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Выберите функции, которые будут запускаться с помощью кнопки регулировки громкости"</string> @@ -1910,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Выходные"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Мероприятие"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Время сна"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Включено"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Отключено"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> приглушает некоторые звуки."</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Произошла внутренняя ошибка, и устройство может работать нестабильно, пока вы не выполните сброс настроек."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Произошла внутренняя ошибка. Обратитесь к производителю устройства за подробными сведениями."</string> @@ -2346,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон заблокирован."</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не удается дублировать на экран"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Используйте другой кабель или повторите попытку."</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Ваше устройство слишком сильно нагрелось. Когда оно остынет, вы снова сможете передавать изображение на другой экран."</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель может не подходить для подключения к дисплеям"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Возможно, подключение дисплеев с помощью этого кабеля USB-C не поддерживается."</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index cba8d477ca3b..c54e7688d57d 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -1908,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"සති අන්තය"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"සිදුවීම"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"නිදා ගනිමින්"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ක්රියාත්මකයි"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ක්රියාවිරහිතයි"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> සමහර ශබ්ද නිහඬ කරමින්"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"ඔබේ උපාංගය සමගින් ගැටලුවක් ඇති අතර, ඔබේ කර්මාන්තශාලා දත්ත යළි සකසන තෙක් එය අස්ථායි විය හැකිය."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"ඔබේ උපාංගය සමගින් අභ්යන්තර ගැටලුවක් ඇත. විස්තර සඳහා ඔබේ නිෂ්පාදක අමතන්න."</string> @@ -2344,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"මයික්රෆෝනය අවහිර කර ඇත"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"සංදර්ශකයට දර්පණය කළ නොහැක"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"වෙනස් කේබලයක් භාවිතා කර නැවත උත්සාහ කරන්න"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"ඔබේ උපාංගය ඉතා උණුසුම් වන අතර එය සිසිල් වන තෙක් සංදර්ශකය වෙත පිළිබිඹු කළ නොහැක"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"කේබලය සංදර්ශක වෙත සහාය නොදැක්විය හැක"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ඔබේ USB-C කේබලයට සංදර්ශකවලට නිසි ලෙස සම්බන්ධ නොවිය හැක"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 61a88201ab05..f215323106c7 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1712,10 +1712,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Môže sledovať vaše interakcie s aplikáciou alebo hardvérovým senzorom a interagovať s aplikáciami za vás."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Povoliť"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Zamietnuť"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Odinštalovať"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikácia zakrýva žiadosť o povolenie, takže vaša odpoveď sa nedá overiť."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Klepnutím na funkciu ju začnite používať:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Výber funkcií, ktoré chcete používať tlačidlom dostupnosti"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Výber funkcií, ktoré chcete používať klávesovou skratkou"</string> @@ -1910,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Víkend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Udalosť"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spánok"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Zapnuté"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Vypnuté"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vypína niektoré zvuky"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Vo vašom zariadení došlo k internému problému. Môže byť nestabilné, kým neobnovíte jeho výrobné nastavenia."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Vo vašom zariadení došlo k internému problému. Ak chcete získať podrobné informácie, obráťte sa na jeho výrobcu."</string> @@ -2346,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofón je blokovaný"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nedá sa zrkadliť do obrazovky"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použite iný kábel a skúste znova"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Zariadenie je príliš horúce a nemôže zrkadliť na obrazovku, kým sa neochladí"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kábel nemusí podporovať obrazovky"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kábel USB‑C sa nemusí dať správne pripojiť k obrazovkám"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 97f91f3408e4..e8ba9dd3ab13 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1712,10 +1712,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Spremlja lahko vaše interakcije z aplikacijo ali tipalom strojne opreme ter komunicira z aplikacijami v vašem imenu."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Dovoli"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Zavrni"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Odmesti"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Aplikacija zakriva zahtevo za dovoljenje, zato ni mogoče potrditi vašega odgovora."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Če želite začeti uporabljati funkcijo, se je dotaknite:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Izberite funkcije, ki jih želite uporabljati z gumbom za dostopnost"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Izberite funkcije, ki jih želite uporabljati z bližnjico na tipki za glasnost"</string> @@ -1910,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Konec tedna"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Dogodek"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spanje"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Vklopljeno"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Izklopljeno"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> izklaplja nekatere zvoke"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Vaša naprava ima notranjo napako in bo morda nestabilna, dokler je ne ponastavite na tovarniške nastavitve."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Vaša naprava ima notranjo napako. Če želite več informacij, se obrnite na proizvajalca."</string> @@ -2346,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ni mogoče zrcaliti zaslona"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Uporabite drug kabel in poskusite znova"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Naprava je pretopla in ne more zrcaliti v zaslon, dokler se ne ohladi"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel morda ne podpira zaslonov"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C se morda ne more ustrezno povezati z zasloni."</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index ea5795d38e44..1d284408a408 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -1908,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Fundjava"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Ngjarje"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Në gjumë"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Aktivizuar"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Çaktivizuar"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> po çaktivizon disa tinguj"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Ka një problem të brendshëm me pajisjen tënde. Ajo mund të jetë e paqëndrueshme derisa të rivendosësh të dhënat në gjendje fabrike."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Ka një problem të brendshëm me pajisjen tënde. Kontakto prodhuesin tënd për detaje."</string> @@ -2344,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni është i bllokuar"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nuk mund të pasqyrojë tek ekrani"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Përdor një kabllo tjetër dhe provo përsëri"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Pajisja është shumë e nxehtë dhe nuk mund të pasqyrojë në ekran derisa të ftohet"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablloja nuk mund të mbështetë ekranet"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kablloja jote USB-C mund të mos lidhet siç duhet me ekranet"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index e5bb5a973817..a6c994e841fa 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1711,10 +1711,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Може да прати интеракције са апликацијом или сензором хардвера и користи апликације уместо вас."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Дозволи"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Одбиј"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Деинсталирај"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Апликација крије захтев за дозволу, па одговор не може да се верификује."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Додирните неку функцију да бисте почели да је користите:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Одаберите функције које ћете користити са дугметом Приступачност"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Одаберите функције за пречицу тастером јачине звука"</string> @@ -1909,10 +1907,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Викенд"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Догађај"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Спавање"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Укључено"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Искључено"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> искључује неке звуке"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Дошло је до интерног проблема у вези са уређајем и можда ће бити нестабилан док не обавите ресетовање на фабричка подешавања."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Дошло је до интерног проблема у вези са уређајем. Потражите детаље од произвођача."</string> @@ -2345,8 +2341,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон је блокиран"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Пресликавање на екран није могуће"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Употребите други кабл и пробајте поново"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Уређај је превише загрејан, па не може да се пресликава на екран док се не охлади"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабл не подржава екране"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабл се не повезује правилно са екранима"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index d80c7590444f..15b7029ca053 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan registrera din användning av en app eller maskinvarusensor och interagera med appar åt dig."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Tillåt"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Neka"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Avinstallera"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"En app döljer behörighetsbegäran så det går inte att verifiera svaret."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tryck på funktioner som du vill aktivera:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Välj vilka funktioner du vill använda med hjälp av tillgänglighetsknappen"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Välj att funktioner att använda med hjälp av volymknappskortkommandot"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"I helgen"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Händelse"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"När jag sover"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"På"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Av"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> stänger av vissa ljud"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Ett internt problem har uppstått i enheten, och det kan hända att problemet kvarstår tills du återställer standardinställningarna."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Ett internt problem har uppstått i enheten. Kontakta tillverkaren om du vill veta mer."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen är blockerad"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det går inte spegla till skärmen"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Använd en annan kabel och försök igen"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Enheten är för varm för att spegla skärmen. Vänta tills den har svalnat"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabeln kanske inte har stöd för skärmar"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Det kanske inte går att ansluta skärmar korrekt med den här USB-C-kabeln"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 3c2a02bc7ce7..aef89eeabde7 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Inaweza kufuatilia mawasiliano yako na programu au kitambuzi cha maunzi na kuwasiliana na programu zingine kwa niaba yako."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Ruhusu"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Kataa"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Ondoa"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Programu inazuia ombi la ruhusa kwa hivyo jibu lako haliwezi kuthibitishwa."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Gusa kipengele ili uanze kukitumia:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Chagua vipengele vya kutumia na kitufe cha zana za ufikivu"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Chagua vipengele vya kutumia na njia ya mkato ya kitufe cha sauti"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Wikendi"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Tukio"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Kulala"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Imewashwa"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Imezimwa"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> inazima baadhi ya sauti"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Kuna hitilafu ya ndani ya kifaa chako, na huenda kisiwe thabiti mpaka urejeshe mipangilio ya kiwandani."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Kuna hitilafu ya ndani ya kifaa chako. Wasiliana na mtengenezaji wa kifaa chako kwa maelezo."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Maikrofoni imezuiwa"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Imeshindwa kuakisi kwenye skrini"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Tumia kebo tofauti kisha ujaribu tena"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Kifaa chako kina joto sana, hakiwezi kuakisi skrini hadi joto lake lipungue"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Huenda kebo haioani na skrini"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Huenda kebo yako ya USB-C isiunganishwe vizuri na skrini"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 79bc2a472108..f81e935353f4 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"ஏதேனும் ஆப்ஸ் அல்லது வன்பொருள் சென்சாரின் உதவியுடன் உரையாடல்களைக் கண்காணித்து உங்கள் சார்பாக ஆப்ஸுடன் உரையாட இச்சேவையால் இயலும்."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"அனுமதி"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"நிராகரி"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"நிறுவல் நீக்கும்"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"அணுகல் கோரிக்கையை ஓர் ஆப்ஸ் மறைப்பதால் உங்கள் பதிலைச் சரிபார்க்க முடியாது."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ஒரு அம்சத்தைப் பயன்படுத்த அதைத் தட்டவும்:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"அணுகல்தன்மை பட்டன் மூலம் பயன்படுத்த விரும்பும் அம்சங்களைத் தேர்வுசெய்யுங்கள்"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"ஒலியளவு விசை ஷார்ட்கட் மூலம் பயன்படுத்த விரும்பும் அம்சங்களைத் தேர்வுசெய்யுங்கள்"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"வார இறுதி"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"நிகழ்வு"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"உறக்கத்தில்"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ஆன்"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ஆஃப்"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> சில ஒலிகளை முடக்குகிறது"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"சாதனத்தில் அகச் சிக்கல் இருக்கிறது, அதனை ஆரம்பநிலைக்கு மீட்டமைக்கும் வரை நிலையற்று இயங்கலாம்."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"சாதனத்தில் அகச் சிக்கல் இருக்கிறது. விவரங்களுக்கு சாதன தயாரிப்பாளரைத் தொடர்புகொள்ளவும்."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"மைக்ரோஃபோன் முடக்கப்பட்டுள்ளது"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"டிஸ்ப்ளேயில் பிரதிபலிக்க முடியவில்லை"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"வெவ்வேறு கேபிள்களைப் பயன்படுத்தி மீண்டும் முயலவும்"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"உங்கள் சாதனம் மிகவும் சூடாக இருப்பதால், அது குறையும் வரை காட்சியைப் பிரதிபலிக்க முடியாது"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"டிஸ்ப்ளேக்களைக் கேபிள் ஆதரிக்காமல் இருக்கக்கூடும்"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"டிஸ்ப்ளேக்களில் உங்கள் USB-C கேபிள் சரியாக இணைக்கப்படாமல் இருக்கக்கூடும்"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"இரட்டைத் திரை"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index a2862ba3123c..671aefb0ee09 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"మీరు ఒక యాప్తో చేసే ఇంటరాక్షన్లను లేదా హార్డ్వేర్ సెన్సార్ను ట్రాక్ చేస్తూ మీ తరఫున యాప్లతో ఇంటరాక్ట్ చేయగలదు."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"అనుమతించండి"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"వద్దు"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"అన్ఇన్స్టాల్ చేయండి"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ఒక యాప్ అనుమతి రిక్వెస్ట్కు అడ్డు తగులుతోంది కాబట్టి మీ సమాధానం వెరిఫై చేయడం సాధ్యం కాదు."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"ఫీచర్ని ఉపయోగించడం ప్రారంభించడానికి, దాన్ని ట్యాప్ చేయండి:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"యాక్సెసిబిలిటీ బటన్తో ఉపయోగించడానికి ఫీచర్లను ఎంచుకోండి"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"వాల్యూమ్ కీ షార్ట్కట్తో ఉపయోగించడానికి ఫీచర్లను ఎంచుకోండి"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"వారాంతం"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ఈవెంట్"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"నిద్రావస్థ"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ఆన్లో ఉంది"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ఆఫ్లో ఉంది"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> కొన్ని ధ్వనులను మ్యూట్ చేస్తోంది"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"మీ పరికరంతో అంతర్గత సమస్య ఏర్పడింది మరియు మీరు ఫ్యాక్టరీ డేటా రీసెట్ చేసే వరకు అస్థిరంగా ఉంటుంది."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"మీ పరికరంతో అంతర్గత సమస్య ఏర్పడింది. వివరాల కోసం మీ తయారీదారుని సంప్రదించండి."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"మైక్రోఫోన్ బ్లాక్ చేయబడింది"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"డిస్ప్లే చేయడానికి మిర్రర్ చేయడం సాధ్యపడదు"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"వేరే కేబుల్ను ఉపయోగించి, మళ్లీ ట్రై చేయండి"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"మీ పరికరం చాలా వెచ్చగా ఉంది, అది చల్లబడే వరకు డిస్ప్లే చేయడానికి మిర్రర్ చేయడం సాధ్యపడదు"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"డిస్ప్లేలను కేబుల్ సపోర్ట్ చేయకపోవచ్చు"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"మీ USB-C కేబుల్, డిస్ప్లేలకు సరిగ్గా కనెక్ట్ కాకపోవచ్చు"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 16e7611788c7..2d5ddf45a16b 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"การควบคุมนี้สามารถติดตามการโต้ตอบของคุณกับแอปหรือกับเซ็นเซอร์ของฮาร์ดแวร์ และโต้ตอบกับแอปต่างๆ แทนคุณ"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"อนุญาต"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"ปฏิเสธ"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"ถอนการติดตั้ง"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"มีแอปหนึ่งบดบังคำขอสิทธิ์ เราจึงยืนยันการตอบกลับของคุณไม่ได้"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"แตะฟีเจอร์เพื่อเริ่มใช้"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"เลือกฟีเจอร์ที่จะใช้กับปุ่มการช่วยเหลือพิเศษ"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"เลือกฟีเจอร์ที่จะใช้กับทางลัดปุ่มปรับระดับเสียง"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"สุดสัปดาห์"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"กิจกรรม"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"นอนหลับ"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"เปิด"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ปิด"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> กำลังปิดเสียงบางรายการ"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"อุปกรณ์ของคุณเกิดปัญหาภายในเครื่อง อุปกรณ์อาจทำงานไม่เสถียรจนกว่าคุณจะรีเซ็ตข้อมูลเป็นค่าเริ่มต้น"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"อุปกรณ์ของคุณเกิดปัญหาภายในเครื่อง โปรดติดต่อผู้ผลิตเพื่อขอรายละเอียดเพิ่มเติม"</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"ไมโครโฟนถูกบล็อก"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"มิเรอร์ไปยังจอแสดงผลไม่ได้"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"โปรดใช้สายอื่นและลองอีกครั้ง"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"อุปกรณ์ร้อนเกินไปและไม่สามารถมิเรอร์ไปยังจอแสดงผลได้จนกว่าจะเย็นลง"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"สายสัญญาณอาจไม่รองรับจอแสดงผล"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"สาย USB-C อาจเชื่อมต่อกับจอแสดงผลอย่างไม่ถูกต้อง"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index b2a7f25fa771..47c4b5e15efd 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Masusubaybayan nito ang iyong mga pakikipag-ugayan sa isang app o hardware na sensor, at puwede itong makipag-ugnayan sa mga app para sa iyo."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Payagan"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Tanggihan"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"I-uninstall"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"May app na pumipigil sa kahilingan sa pahintulot kaya hindi ma-verify ang iyong sagot."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"I-tap ang isang feature para simulan itong gamitin:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Pumili ng mga feature na gagana sa pamamagitan ng button ng accessibility"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Pumili ng mga feature na gagana sa pamamagitan ng shortcut ng volume key"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Weekend"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Event"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Pag-sleep"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Naka-on"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Naka-off"</string> <string name="muted_by" msgid="91464083490094950">"Minu-mute ng <xliff:g id="THIRD_PARTY">%1$s</xliff:g> ang ilang tunog"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"May internal na problema sa iyong device, at maaaring hindi ito maging stable hanggang sa i-reset mo ang factory data."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"May internal na problema sa iyong device. Makipag-ugnayan sa iyong manufacturer upang malaman ang mga detalye."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Naka-block ang mikropono"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Hindi makapag-mirror sa display"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gumamit ng ibang cable at subukan ulit"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Masyadong mainit ang iyong device at hindi ito makakapag-mirror sa display hanggang sa lumamig ito"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Posibleng hindi sinusuportahan ng cable ang mga display"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Posibleng hindi kumonekta nang maayos sa mga display ang iyong USB-C cable"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index eb58785bbf5f..03536fe12c4f 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Bir uygulama veya donanım sensörüyle etkileşimlerinizi takip edebilir ve sizin adınıza uygulamalarla etkileşimde bulunabilir."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"İzin ver"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Reddet"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Kaldır"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Bir uygulama, izin isteğini gizlediğinden yanıtınız doğrulanamıyor."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Kullanmaya başlamak için bir özelliğe dokunun:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Erişilebilirlik düğmesiyle kullanılacak özellikleri seçin"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Ses tuşu kısayoluyla kullanılacak özellikleri seçin"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Hafta sonu"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Etkinlik"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Uyku"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Açık"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Kapalı"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> bazı sesleri kapatıyor"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Cihazınızla ilgili dahili bir sorun oluştu ve fabrika verilerine sıfırlama işlemi gerçekleştirilene kadar kararsız çalışabilir."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Cihazınızla ilgili dahili bir sorun oluştu. Ayrıntılı bilgi için üreticinizle iletişim kurun."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon engellenmiş"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekrana yansıtılamıyor"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Farklı kablo kullanarak tekrar deneyin"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Cihazınız çok ısındığı için soğuyana kadar ekrana yansıtılamaz"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablo, ekranları desteklemeyebilir"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kablonuz ekranlara doğru şekilde bağlanamayabilir"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 393fa761a086..24cbc3f634e8 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1910,10 +1910,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"На вихідних"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Подія"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Під час сну"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Увімкнено"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Вимкнено"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> вимикає деякі звуки"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Через внутрішню помилку ваш пристрій може працювати нестабільно. Відновіть заводські налаштування."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"На пристрої сталася внутрішня помилка. Зв’яжіться з виробником пристрою, щоб дізнатися більше."</string> @@ -2346,8 +2344,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрофон заблоковано"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Неможливо дублювати на дисплей"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Скористайтесь іншим кабелем і повторіть спробу"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Пристрій перегрівся й не зможе дублювати контент на дисплей, поки не охолоне"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель може не підтримувати дисплеї"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ваш кабель USB-C може не підключатися до дисплеїв належним чином"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 9ea7348ef577..728b5eeaf678 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -1908,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"ویک اینڈ"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ایونٹ"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"سونا"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"آن ہے"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"آف ہے"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> کچھ آوازوں کو خاموش کر رہا ہے"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"آپ کے آلہ میں ایک داخلی مسئلہ ہے اور جب تک آپ فیکٹری ڈیٹا کو دوبارہ ترتیب نہیں دے دیتے ہیں، ہوسکتا ہے کہ یہ غیر مستحکم رہے۔"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"آپ کے آلہ میں ایک داخلی مسئلہ ہے۔ تفصیلات کیلئے اپنے مینوفیکچرر سے رابطہ کریں۔"</string> @@ -2344,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"مائیکروفون مسدود ہے"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ڈسپلے پر دو طرفہ مطابقت پذیری ممکن نہیں ہے"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"مختلف کیبل استعمال کریں اور دوبارہ کوشش کریں"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"آپ کا آلہ بہت گرم ہے اور جب تک یہ ٹھنڈا نہیں ہو جاتا اس وقت تک ڈسپلے میں عکس دو طرفہ مطابقت پذیر نہیں ہو سکتا"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ہو سکتا ہے کہ کیبل ڈسپلیز کو سپورٹ نہ کرے"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ہو سکتا ہے کہ آپ کی USB-C کیبل مناسب طریقے سے ڈسپلیز سے منسلک نہ ہو"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"دوہری اسکرین"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 78f352906d1b..1e1807ef1337 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -1908,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Dam olish kunlari"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Tadbir"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Uyquda"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Yoniq"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Oʻchiq"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ayrim tovushlarni ovozsiz qilgan"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Qurilmangiz bilan bog‘liq ichki muammo mavjud. U zavod sozlamalari tiklanmaguncha barqaror ishlamasligi mumkin."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Qurilmangiz bilan bog‘liq ichki muammo mavjud. Tafsilotlar uchun qurilmangiz ishlab chiqaruvchisiga murojaat qiling."</string> @@ -2344,8 +2342,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon bloklandi"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeyga translatsiya qilinmaydi"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Boshqa kabel yordamida qayta urining"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Qurilma qizib ketdi va u sovimaguncha displeyni aks ettirmaydi"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeylar bilan ishlamasligi mumkin"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabelingiz displeylarga toʻgʻri ulanmasligi mumkin"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Ikkita ekran"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 1b6ededd1d8a..94ebd97b066d 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Dịch vụ này có thể theo dõi các hoạt động tương tác của bạn với một ứng dụng hoặc bộ cảm biến phần cứng, cũng như có thể thay mặt bạn tương tác với các ứng dụng."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Cho phép"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Từ chối"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Gỡ cài đặt"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"Một ứng dụng đang che khuất yêu cầu quyền này nên chúng tôi không thể xác minh phản hồi của bạn."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Nhấn vào một tính năng để bắt đầu sử dụng:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Chọn các tính năng để dùng với nút hỗ trợ tiếp cận"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Chọn các tính năng để dùng với phím tắt là phím âm lượng"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Cuối tuần"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Sự kiện"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Ngủ"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Bật"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Tắt"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> đang tắt một số âm thanh"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Đã xảy ra sự cố nội bộ với thiết bị của bạn và thiết bị có thể sẽ không ổn định cho tới khi bạn thiết lập lại dữ liệu ban đầu."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Đã xảy ra sự cố nội bộ với thiết bị. Hãy liên hệ với nhà sản xuất của bạn để biết chi tiết."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Micrô đang bị chặn"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Không chiếu được nội dung lên màn hình"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Hãy dùng một cáp khác rồi thử lại"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Thiết bị của bạn quá nóng nên không phản chiếu được nội dung lên màn hình. Hãy để thiết bị nguội bớt"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Có thể cáp không hỗ trợ màn hình"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Có thể cáp USB-C của bạn chưa kết nối đúng cách với màn hình"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 676ab00f0464..108f507b07cd 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"此功能可以跟踪您与应用或硬件传感器的互动,并代表您与应用互动。"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"允许"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"拒绝"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"卸载"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"应用遮挡了权限请求,因此我们无法验证您的回复。"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"点按相应功能即可开始使用:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"选择可通过“无障碍”按钮使用的功能"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"选择可通过音量键快捷方式使用的功能"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"周末"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"活动"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"睡眠"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"已启用"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"已停用"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>正在将某些音效设为静音"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"您的设备内部出现了问题。如果不将设备恢复出厂设置,设备运行可能会不稳定。"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"您的设备内部出现了问题。请联系您的设备制造商了解详情。"</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"麦克风已被屏蔽"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"无法镜像到显示屏"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"请改用其他数据线并重试"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"设备温度过高,在温度降下来之前,设备无法镜像到显示屏"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"数据线可能不支持显示屏"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"您的 USB-C 数据线可能没有正确连接到显示屏"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"双屏幕"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 76dabaf688f5..a37f9a878240 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"這項功能會追蹤你與應用程式或硬件感應器的互動,並代表你直接與應用程式互動。"</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"允許"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"拒絕"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"解除安裝"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"有應用程式阻擋權限要求,因此系統無法驗證你的回應。"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"輕按即可開始使用所需功能:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"選擇要配搭無障礙功能按鈕使用的功能"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"選擇要用音量快速鍵的功能"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"週末"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"活動"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"睡眠"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"已開啟"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"已關閉"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>正將某些音效設為靜音"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"你裝置的系統發生問題,回復原廠設定後即可解決該問題。"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"你裝置的系統發生問題,請聯絡你的製造商瞭解詳情。"</string> @@ -2344,7 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"已封鎖麥克風"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他連接線,然後再試一次"</string> - <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"裝置過熱,請等到降溫後再將畫面鏡像投放至螢幕"</string> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"裝置過熱,請等到降溫後再將畫面鏡像投射至螢幕"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"連接線可能不支援顯示屏"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"你的 USB-C 連接線可能未妥善連接顯示屏"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index fc5f262f3ebc..5dced74b698e 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1908,10 +1908,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"週末"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"活動"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"睡眠"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"已啟用"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"已停用"</string> <string name="muted_by" msgid="91464083490094950">"「<xliff:g id="THIRD_PARTY">%1$s</xliff:g>」正在關閉部分音效"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"你的裝置發生內部問題,必須將裝置恢復原廠設定才能解除不穩定狀態。"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"你的裝置發生內部問題,詳情請洽裝置製造商。"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index a88d832b3636..aaa1787e1534 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1710,10 +1710,8 @@ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Ingalandela ukusebenzisana kwakho nohlelo lokusebenza noma inzwa yehadiwe, nokusebenzisana nezinhlelo zokusebenza engxenyeni yakho."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Vumela"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Phika"</string> - <!-- no translation found for accessibility_dialog_button_uninstall (2952465517671708108) --> - <skip /> - <!-- no translation found for accessibility_dialog_touch_filtered_warning (3741940116597822451) --> - <skip /> + <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Khipha"</string> + <string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"I-app ifihla isicelo semvume ngakho impendulo yakho ayikwazi ukuqinisekiswa."</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Thepha isici ukuqala ukusisebenzisa:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Khetha izici ongazisebenzisa nenkinobho yokufinyeleleka"</string> <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Khetha izici ongazisebenzisa nesinqamuleli sokhiye wevolumu"</string> @@ -1908,10 +1906,8 @@ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Ngempelasonto"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Umcimbi"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Ulele"</string> - <!-- no translation found for zen_mode_implicit_activated (2634285680776672994) --> - <skip /> - <!-- no translation found for zen_mode_implicit_deactivated (8688441768371501750) --> - <skip /> + <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"Kuvuliwe"</string> + <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Kuvaliwe"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ithulisa eminye imisindo"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Kukhona inkinga yangaphakathi ngedivayisi yakho, futhi ingase ibe engazinzile kuze kube yilapho usetha kabusha yonke idatha."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Kukhona inkinga yangaphakathi ngedivayisi yakho. Xhumana nomkhiqizi wakho ukuze uthole imininingwane."</string> @@ -2344,8 +2340,7 @@ <string name="mic_access_off_toast" msgid="8111040892954242437">"Imakrofoni ivinjiwe"</string> <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ayikwazi ukufanisa nesibonisi"</string> <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Sebenzisa ikhebuli ehlukile bese uyazama futhi"</string> - <!-- no translation found for connected_display_thermally_unavailable_notification_content (9205758199439955949) --> - <skip /> + <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Idivayisi yakho ifudumele kakhulu futhi ayikwazi ukwenza isibuko kusibonisi size siphole"</string> <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ikhebuli ingase ingasekeli iziboniso"</string> <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ikhebuli yakho ye-USB-C ingase ingaxhumi kahle kuzibonisi"</string> <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Isikrini esikabili"</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 6cd6eb4b8df9..98897d89bf6d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4336,6 +4336,9 @@ <!-- True if assistant app should be pinned via Pinner Service --> <bool name="config_pinnerAssistantApp">false</bool> + <!-- Bytes that the PinnerService will pin for WebView --> + <integer name="config_pinnerWebviewPinBytes">0</integer> + <!-- Number of days preloaded file cache should be preserved on a device before it can be deleted --> <integer name="config_keepPreloadsMinDays">7</integer> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index eed186ad3702..73a7e4296a57 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6350,4 +6350,20 @@ ul.</string> <string name="keyboard_layout_notification_multiple_selected_title">Physical keyboards configured</string> <!-- Notification message when multiple keyboards with selected layouts have been connected the first time simultaneously [CHAR LIMIT=NOTIF_BODY] --> <string name="keyboard_layout_notification_multiple_selected_message">Tap to view keyboards</string> + + <!-- Private profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] --> + <string name="profile_label_private">Private</string> + <!-- Clone profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] --> + <string name="profile_label_clone">Clone</string> + <!-- Work profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] --> + <string name="profile_label_work">Work</string> + <!-- 2nd Work profile label on a screen in case a device has more than one work profiles. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] --> + <string name="profile_label_work_2">Work 2</string> + <!-- 3rd Work profile label on a screen in case a device has more than two work profiles. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] --> + <string name="profile_label_work_3">Work 3</string> + <!-- Test profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] --> + <string name="profile_label_test">Test</string> + <!-- Communal profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] --> + <string name="profile_label_communal">Communal</string> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 24b39bc1225e..017688ade387 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1086,6 +1086,13 @@ <java-symbol type="string" name="managed_profile_label_badge_3" /> <java-symbol type="string" name="clone_profile_label_badge" /> <java-symbol type="string" name="private_profile_label_badge" /> + <java-symbol type="string" name="profile_label_private" /> + <java-symbol type="string" name="profile_label_clone" /> + <java-symbol type="string" name="profile_label_work" /> + <java-symbol type="string" name="profile_label_work_2" /> + <java-symbol type="string" name="profile_label_work_3" /> + <java-symbol type="string" name="profile_label_test" /> + <java-symbol type="string" name="profile_label_communal" /> <java-symbol type="string" name="mediasize_unknown_portrait" /> <java-symbol type="string" name="mediasize_unknown_landscape" /> <java-symbol type="string" name="mediasize_iso_a0" /> @@ -3378,6 +3385,7 @@ <java-symbol type="bool" name="config_pinnerCameraApp" /> <java-symbol type="bool" name="config_pinnerHomeApp" /> <java-symbol type="bool" name="config_pinnerAssistantApp" /> + <java-symbol type="integer" name="config_pinnerWebviewPinBytes" /> <java-symbol type="string" name="config_doubleTouchGestureEnableFile" /> diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java index d4a88c49b38f..a3a1d3a5e7a2 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java @@ -34,9 +34,7 @@ import android.content.pm.ApplicationInfo; import android.os.Build; import android.os.Parcel; import android.os.RemoteException; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; import com.google.common.truth.Expect; @@ -150,7 +148,7 @@ public final class ProgramListTest { @Rule public final Expect mExpect = Expect.create(); @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Test public void getIdentifierTypes_forFilter() { @@ -631,8 +629,8 @@ public final class ProgramListTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getProgramInfos() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); createRadioTuner(); mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); registerListCallbacks(/* numCallbacks= */ 1); @@ -648,8 +646,8 @@ public final class ProgramListTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getProgramInfos_withIdNotFound() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); createRadioTuner(); mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); registerListCallbacks(/* numCallbacks= */ 1); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java index 03de1430fec8..89464d14d1c7 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java @@ -32,9 +32,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Parcel; import android.os.RemoteException; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import org.junit.Rule; @@ -168,7 +166,7 @@ public final class RadioManagerTest { private ICloseHandle mCloseHandleMock; @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Test public void getType_forBandDescriptor() { @@ -962,22 +960,25 @@ public final class RadioManagerTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isSignalAcquired_forProgramInfo() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + assertWithMessage("Signal acquisition status for HD program info") .that(HD_PROGRAM_INFO.isSignalAcquired()).isTrue(); } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isHdSisAvailable_forProgramInfo() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + assertWithMessage("SIS information acquisition status for HD program") .that(HD_PROGRAM_INFO.isHdSisAvailable()).isTrue(); } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isHdAudioAvailable_forProgramInfo() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + assertWithMessage("Audio acquisition status for HD program") .that(HD_PROGRAM_INFO.isHdAudioAvailable()).isFalse(); } diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java index 3891accbba44..7b9121eb6085 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java @@ -22,10 +22,7 @@ import static org.junit.Assert.assertThrows; import android.graphics.Bitmap; import android.os.Parcel; -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.platform.test.flag.junit.SetFlagsRule; import org.junit.Rule; import org.junit.Test; @@ -52,7 +49,7 @@ public final class RadioMetadataTest { private Bitmap mBitmapValue; @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Test public void describeContents_forClock() { @@ -128,8 +125,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void putStringArray_withIllegalKey_throwsException() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); String invalidStringArrayKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG; IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { @@ -142,8 +139,9 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void putStringArray_withNullKey_throwsException() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE); }); @@ -153,8 +151,9 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void putStringArray_withNullString_throwsException() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null); }); @@ -281,8 +280,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getStringArray_withKeyInMetadata() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); String key = RadioMetadata.METADATA_KEY_UFIDS; RadioMetadata metadata = mBuilder.putStringArray(key, UFIDS_VALUE).build(); @@ -291,8 +290,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getStringArray_withKeyNotInMetadata() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); String key = RadioMetadata.METADATA_KEY_UFIDS; RadioMetadata metadata = mBuilder.build(); @@ -305,8 +304,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getStringArray_withNullKey() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); RadioMetadata metadata = mBuilder.build(); NullPointerException thrown = assertThrows(NullPointerException.class, () -> { @@ -318,8 +317,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void getStringArray_withInvalidKey() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); String invalidClockKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG; RadioMetadata metadata = mBuilder.build(); @@ -413,7 +412,6 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void writeToParcel_forRadioMetadata() { RadioMetadata metadataExpected = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) @@ -430,8 +428,8 @@ public final class RadioMetadataTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void writeToParcel_forRadioMetadata_withStringArrayTypeMetadata() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); RadioMetadata metadataExpected = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) @@ -443,7 +441,7 @@ public final class RadioMetadataTest { parcel.setDataPosition(0); RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel); - assertWithMessage("Radio metadata created from parcel") + assertWithMessage("Radio metadata created from parcel with string array type metadata") .that(metadataFromParcel).isEqualTo(metadataExpected); } } diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java index 7ca806b49b68..4841711f712d 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java @@ -36,10 +36,7 @@ import android.content.pm.ApplicationInfo; import android.graphics.Bitmap; import android.os.Build; import android.os.RemoteException; -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.platform.test.flag.junit.SetFlagsRule; import org.junit.After; import org.junit.Before; @@ -84,7 +81,7 @@ public final class TunerAdapterTest { private RadioTuner.Callback mCallbackMock; @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() throws Exception { @@ -613,9 +610,9 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogSupported() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM)) .thenReturn(true); @@ -626,9 +623,9 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogNotSupported() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) .thenReturn(false); when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true); @@ -640,9 +637,9 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void isConfigFlagSet_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false); @@ -683,8 +680,8 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void setConfigFlag_withForceAnalogWhenFmForceAnalogSupported() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false); @@ -695,8 +692,8 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void setConfigFlag_withForceAnalogWhenFmForceAnalogNotSupported() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) .thenReturn(false); @@ -709,9 +706,9 @@ public final class TunerAdapterTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void setConfigFlag_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java index 49a7ba855908..15bb66b3e303 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java @@ -40,10 +40,7 @@ import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; import android.hardware.radio.UniqueProgramIdentifier; import android.os.ServiceSpecificException; -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.platform.test.flag.junit.SetFlagsRule; import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase; @@ -159,7 +156,7 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { @Rule public final Expect expect = Expect.create(); @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Override protected void initializeSession(StaticMockitoSessionBuilder builder) { @@ -317,9 +314,10 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void identifierToHalProgramIdentifier_withFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); ProgramSelector.Identifier hdLocationId = createHdStationLocationIdWithFlagEnabled(); + ProgramIdentifier halHdLocationId = ConversionUtils.identifierToHalProgramIdentifier(hdLocationId); @@ -336,10 +334,11 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void identifierFromHalProgramIdentifier_withFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); ProgramSelector.Identifier hdLocationIdExpected = createHdStationLocationIdWithFlagEnabled(); + ProgramSelector.Identifier hdLocationId = ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_HD_STATION_LOCATION_ID); @@ -348,8 +347,9 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void identifierFromHalProgramIdentifier_withFlagDisabled_returnsNull() { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + ProgramSelector.Identifier hdLocationId = ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_HD_STATION_LOCATION_ID); @@ -432,8 +432,8 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void programSelectorMeetsSdkVersionRequirement_withLowerVersionSecondaryId() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); ProgramSelector hdSelector = createHdSelectorWithFlagEnabled(); expect.withMessage("Selector %s with secondary id requiring higher-version SDK version", @@ -449,8 +449,8 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void programSelectorMeetsSdkVersionRequirement_withRequiredVersionAndFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); ProgramSelector hdSelector = createHdSelectorWithFlagEnabled(); expect.withMessage("Selector %s with required SDK version and feature flag enabled", @@ -548,8 +548,8 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void configFlagMeetsSdkVersionRequirement_withRequiredSdkVersionAndFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); int halForceAmAnalogFlag = ConfigFlag.FORCE_ANALOG_FM; expect.withMessage("Force Analog FM flag with required SDK version and feature flag" @@ -558,8 +558,8 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void configFlagMeetsSdkVersionRequirement_withRequiredSdkVersionAndFlagDisabled() { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); int halForceAmAnalogFlag = ConfigFlag.FORCE_ANALOG_FM; expect.withMessage("Force Analog FM with required SDK version and with feature flag" @@ -586,8 +586,9 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void radioMetadataFromHalMetadata_withFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + RadioMetadata convertedMetadata = ConversionUtils.radioMetadataFromHalMetadata( new Metadata[]{TEST_HAL_SONG_TITLE, TEST_HAL_HD_SUBCHANNELS, TEST_HAL_ALBUM_ART}); @@ -605,8 +606,9 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void radioMetadataFromHalMetadata_withFlagDisabled() { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); + RadioMetadata convertedMetadata = ConversionUtils.radioMetadataFromHalMetadata( new Metadata[]{TEST_HAL_SONG_TITLE, TEST_HAL_HD_SUBCHANNELS, TEST_HAL_ALBUM_ART}); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 7bef5abc3d63..296c45136292 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -53,10 +53,7 @@ import android.os.ParcelableException; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; -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.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.util.ArraySet; @@ -164,7 +161,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Rule public final Expect expect = Expect.create(); @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Override protected void initializeSession(StaticMockitoSessionBuilder builder) { @@ -1231,8 +1228,8 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) public void onConfigFlagUpdated_withRequiredFlagEnabled_invokesCallbacks() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED); openAidlClients(/* numClients= */ 1); mHalTunerCallback.onConfigFlagUpdated(ConfigFlag.FORCE_ANALOG_FM, true); @@ -1242,9 +1239,9 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void onConfigFlagUpdated_withRequiredFlagDisabled_doesNotInvokeCallbacks() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_HD_RADIO_IMPROVED); openAidlClients(/* numClients= */ 1); mHalTunerCallback.onConfigFlagUpdated(ConfigFlag.FORCE_ANALOG_FM, true); diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index d47d7891d0e4..1cdcb376effc 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -17,11 +17,15 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED; +import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED; +import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARING; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -29,14 +33,20 @@ import android.content.ComponentName; import android.content.ContentCaptureOptions; import android.content.Context; import android.content.pm.ParceledListSlice; +import android.graphics.Insets; import android.os.Handler; -import android.os.Looper; +import android.os.RemoteException; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.SparseArray; +import android.view.View; +import android.view.autofill.AutofillId; import android.view.contentprotection.ContentProtectionEventProcessor; import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,8 +66,9 @@ import java.util.List; * <p>Run with: {@code atest * FrameworksCoreTests:android.view.contentcapture.MainContentCaptureSessionTest} */ -@RunWith(AndroidJUnit4.class) +@RunWith(AndroidTestingRunner.class) @SmallTest +@TestableLooper.RunWithLooper public class MainContentCaptureSessionTest { private static final int BUFFER_SIZE = 100; @@ -75,6 +86,8 @@ public class MainContentCaptureSessionTest { private static final ContentCaptureManager.StrippedContext sStrippedContext = new ContentCaptureManager.StrippedContext(sContext); + private TestableLooper mTestableLooper; + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock private IContentCaptureManager mMockSystemServerInterface; @@ -83,12 +96,18 @@ public class MainContentCaptureSessionTest { @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager; + @Before + public void setup() { + mTestableLooper = TestableLooper.get(this); + } + @Test public void onSessionStarted_contentProtectionEnabled_processorCreated() { MainContentCaptureSession session = createSession(); assertThat(session.mContentProtectionEventProcessor).isNull(); session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + mTestableLooper.processAllMessages(); assertThat(session.mContentProtectionEventProcessor).isNotNull(); } @@ -102,6 +121,7 @@ public class MainContentCaptureSessionTest { session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + mTestableLooper.processAllMessages(); assertThat(session.mContentProtectionEventProcessor).isNull(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -122,6 +142,7 @@ public class MainContentCaptureSessionTest { session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + mTestableLooper.processAllMessages(); assertThat(session.mContentProtectionEventProcessor).isNull(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -142,6 +163,7 @@ public class MainContentCaptureSessionTest { session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + mTestableLooper.processAllMessages(); assertThat(session.mContentProtectionEventProcessor).isNull(); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -153,6 +175,7 @@ public class MainContentCaptureSessionTest { session.mComponentName = null; session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + mTestableLooper.processAllMessages(); assertThat(session.mContentProtectionEventProcessor).isNull(); } @@ -166,6 +189,7 @@ public class MainContentCaptureSessionTest { session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; session.sendEvent(EVENT); + mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isNull(); @@ -180,6 +204,7 @@ public class MainContentCaptureSessionTest { session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; session.sendEvent(EVENT); + mTestableLooper.processAllMessages(); verify(mMockContentProtectionEventProcessor).processEvent(EVENT); assertThat(session.mEvents).isNull(); @@ -194,6 +219,7 @@ public class MainContentCaptureSessionTest { session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; session.sendEvent(EVENT); + mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isNotNull(); @@ -206,6 +232,7 @@ public class MainContentCaptureSessionTest { session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; session.sendEvent(EVENT); + mTestableLooper.processAllMessages(); verify(mMockContentProtectionEventProcessor).processEvent(EVENT); assertThat(session.mEvents).isNotNull(); @@ -220,6 +247,7 @@ public class MainContentCaptureSessionTest { /* enableContentProtectionReceiver= */ true); session.sendEvent(EVENT); + mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isNull(); @@ -236,6 +264,7 @@ public class MainContentCaptureSessionTest { session.mDirectServiceInterface = mMockContentCaptureDirectManager; session.flush(REASON); + mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); verifyZeroInteractions(mMockContentCaptureDirectManager); @@ -252,6 +281,7 @@ public class MainContentCaptureSessionTest { session.mDirectServiceInterface = mMockContentCaptureDirectManager; session.flush(REASON); + mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); verifyZeroInteractions(mMockContentCaptureDirectManager); @@ -269,6 +299,7 @@ public class MainContentCaptureSessionTest { session.mDirectServiceInterface = mMockContentCaptureDirectManager; session.flush(REASON); + mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isEmpty(); @@ -286,6 +317,7 @@ public class MainContentCaptureSessionTest { session.mDirectServiceInterface = mMockContentCaptureDirectManager; session.flush(REASON); + mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockContentProtectionEventProcessor); assertThat(session.mEvents).isEmpty(); @@ -298,6 +330,7 @@ public class MainContentCaptureSessionTest { session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; session.destroySession(); + mTestableLooper.processAllMessages(); verify(mMockSystemServerInterface).finishSession(anyInt()); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -311,6 +344,7 @@ public class MainContentCaptureSessionTest { session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; session.resetSession(/* newState= */ 0); + mTestableLooper.processAllMessages(); verifyZeroInteractions(mMockSystemServerInterface); verifyZeroInteractions(mMockContentProtectionEventProcessor); @@ -318,6 +352,111 @@ public class MainContentCaptureSessionTest { assertThat(session.mContentProtectionEventProcessor).isNull(); } + @Test + @SuppressWarnings("GuardedBy") + public void notifyContentCaptureEvents_notStarted_ContentCaptureDisabled_ProtectionDisabled() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ false, + /* enableContentProtectionReceiver= */ false); + MainContentCaptureSession session = createSession(options); + + notifyContentCaptureEvents(session); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentCaptureDirectManager); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isNull(); + } + + @Test + @SuppressWarnings("GuardedBy") + public void notifyContentCaptureEvents_started_ContentCaptureDisabled_ProtectionDisabled() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ false, + /* enableContentProtectionReceiver= */ false); + MainContentCaptureSession session = createSession(options); + + session.onSessionStarted(0x2, null); + notifyContentCaptureEvents(session); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentCaptureDirectManager); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isNull(); + } + + @Test + @SuppressWarnings("GuardedBy") + public void notifyContentCaptureEvents_notStarted_ContentCaptureEnabled_ProtectionEnabled() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + notifyContentCaptureEvents(session); + mTestableLooper.processAllMessages(); + + verifyZeroInteractions(mMockContentCaptureDirectManager); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + assertThat(session.mEvents).isNull(); + } + + @Test + @SuppressWarnings("GuardedBy") + public void notifyContentCaptureEvents_started_ContentCaptureEnabled_ProtectionEnabled() + throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSession session = createSession(options); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.onSessionStarted(0x2, null); + // Override the processor for interaction verification. + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + notifyContentCaptureEvents(session); + mTestableLooper.processAllMessages(); + + // Force flush will happen twice. + verify(mMockContentCaptureDirectManager, times(1)) + .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARING), any()); + verify(mMockContentCaptureDirectManager, times(1)) + .sendEvents(any(), eq(FLUSH_REASON_VIEW_TREE_APPEARED), any()); + // Other than the five view events, there will be two additional tree appearing events. + verify(mMockContentProtectionEventProcessor, times(7)).processEvent(any()); + assertThat(session.mEvents).isEmpty(); + } + + /** Simulates the regular content capture events sequence. */ + private void notifyContentCaptureEvents(final MainContentCaptureSession session) { + final ArrayList<Object> events = new ArrayList<>( + List.of( + prepareView(session), + prepareView(session), + new AutofillId(0), + prepareView(session), + Insets.of(0, 0, 0, 0) + ) + ); + + final SparseArray<ArrayList<Object>> contentCaptureEvents = new SparseArray<>(); + contentCaptureEvents.set(session.getId(), events); + + session.notifyContentCaptureEvents(contentCaptureEvents); + } + + private View prepareView(final MainContentCaptureSession session) { + final View view = new View(sContext); + view.setContentCaptureSession(session); + return view; + } + private static ContentCaptureOptions createOptions( boolean enableContentCaptureReceiver, ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) { @@ -354,7 +493,7 @@ public class MainContentCaptureSessionTest { new MainContentCaptureSession( sStrippedContext, manager, - new Handler(Looper.getMainLooper()), + Handler.createAsync(mTestableLooper.getLooper()), mMockSystemServerInterface); session.mComponentName = COMPONENT_NAME; return session; diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java index 6229530dc33f..3147eac30705 100644 --- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java +++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java @@ -21,7 +21,7 @@ import static android.os.PerformanceHintManager.Session.CPU_LOAD_UP; import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN; -import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF; +import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE; import static android.window.SystemPerformanceHinter.HINT_ADPF; import static android.window.SystemPerformanceHinter.HINT_ALL; import static android.window.SystemPerformanceHinter.HINT_SF_EARLY_WAKEUP; @@ -170,7 +170,7 @@ public class SystemPerformanceHinterTests { // Verify we call SF verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), @@ -262,7 +262,7 @@ public class SystemPerformanceHinterTests { // Verify we call SF and perf manager to clean up verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), @@ -283,7 +283,7 @@ public class SystemPerformanceHinterTests { // Verify we call SF and perf manager to clean up verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), @@ -334,7 +334,7 @@ public class SystemPerformanceHinterTests { // Verify we call SF and perf manager to clean up verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), @@ -385,7 +385,7 @@ public class SystemPerformanceHinterTests { session1.close(); verify(mTransaction).setFrameRateSelectionStrategy( eq(mDefaultDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mDefaultDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), @@ -410,7 +410,7 @@ public class SystemPerformanceHinterTests { anyInt()); verify(mTransaction).setFrameRateSelectionStrategy( eq(mSecondaryDisplayRoot), - eq(FRAME_RATE_SELECTION_STRATEGY_SELF)); + eq(FRAME_RATE_SELECTION_STRATEGY_PROPAGATE)); verify(mTransaction).setFrameRateCategory( eq(mSecondaryDisplayRoot), eq(FRAME_RATE_CATEGORY_DEFAULT), diff --git a/core/tests/timetests/TEST_MAPPING b/core/tests/timetests/TEST_MAPPING index 57480445a7b0..0796d5af36d0 100644 --- a/core/tests/timetests/TEST_MAPPING +++ b/core/tests/timetests/TEST_MAPPING @@ -1,6 +1,5 @@ { - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + "presubmit": [ { "name": "FrameworksTimeCoreTests" } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index f19acbe023b8..d36ac3914ff3 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -991,12 +991,6 @@ "group": "WM_DEBUG_WINDOW_INSETS", "at": "com\/android\/server\/wm\/InsetsSourceProvider.java" }, - "-1176488860": { - "message": "SURFACE isSecure=%b: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowSurfaceController.java" - }, "-1164930508": { "message": "Moving to RESUMED: %s (starting new instance) callers=%s", "level": "VERBOSE", @@ -3277,6 +3271,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "810599500": { + "message": "SURFACE isSecure=%b: %s", + "level": "INFO", + "group": "WM_SHOW_TRANSACTIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "829434921": { "message": "Draw state now committed in %s", "level": "VERBOSE", diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig index 9a0a22a08a0c..6c81a608241c 100644 --- a/graphics/java/android/framework_graphics.aconfig +++ b/graphics/java/android/framework_graphics.aconfig @@ -5,4 +5,11 @@ flag { namespace: "core_graphics" description: "Add a function without unused exact param for computeBounds." bug: "304478551" +} + +flag { + name: "yuv_image_compress_to_ultra_hdr" + namespace: "core_graphics" + description: "Feature flag for YUV image compress to Ultra HDR." + bug: "308978825" }
\ No newline at end of file diff --git a/graphics/java/android/graphics/Insets.java b/graphics/java/android/graphics/Insets.java index 1e03c530aed7..2b170b9a149a 100644 --- a/graphics/java/android/graphics/Insets.java +++ b/graphics/java/android/graphics/Insets.java @@ -29,6 +29,7 @@ import android.os.Parcelable; * Insets are immutable so may be treated as values. * */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class Insets implements Parcelable { public static final @NonNull Insets NONE = new Insets(0, 0, 0, 0); diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java index 2781ac4bf1da..e5c620bdc1b4 100644 --- a/graphics/java/android/graphics/Point.java +++ b/graphics/java/android/graphics/Point.java @@ -24,6 +24,7 @@ import android.os.Parcelable; /** * Point holds two integer coordinates */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Point implements Parcelable { public int x; public int y; diff --git a/graphics/java/android/graphics/PointF.java b/graphics/java/android/graphics/PointF.java index ed9df145f880..3531785c7175 100644 --- a/graphics/java/android/graphics/PointF.java +++ b/graphics/java/android/graphics/PointF.java @@ -23,6 +23,7 @@ import android.os.Parcelable; /** * PointF holds two float coordinates */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PointF implements Parcelable { public float x; public float y; diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index 1a522bd5e794..411a10b85bd2 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -45,6 +45,7 @@ import java.util.regex.Pattern; * into the column and row described by its left and top coordinates, but not * those of its bottom and right. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class Rect implements Parcelable { public int left; public int top; diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java index 1d294d51a235..ff50a0c55e6c 100644 --- a/graphics/java/android/graphics/RectF.java +++ b/graphics/java/android/graphics/RectF.java @@ -32,6 +32,7 @@ import java.io.PrintWriter; * the rectangle's width and height. Note: most methods do not check to see that * the coordinates are sorted correctly (i.e. left <= right and top <= bottom). */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class RectF implements Parcelable { public float left; public float top; diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java index 6b5238b20cdc..ce35b55d526f 100644 --- a/graphics/java/android/graphics/YuvImage.java +++ b/graphics/java/android/graphics/YuvImage.java @@ -16,6 +16,9 @@ package android.graphics; +import com.android.graphics.flags.Flags; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import java.io.OutputStream; @@ -243,6 +246,36 @@ public class YuvImage { new byte[WORKING_COMPRESS_STORAGE]); } + /** + * Compress the HDR image into JPEG/R format. + * + * Sample usage: + * hdr_image.compressToJpegR(sdr_image, 90, stream); + * + * For the SDR image, only YUV_420_888 image format is supported, and the following + * color spaces are supported: + * ColorSpace.Named.SRGB, + * ColorSpace.Named.DISPLAY_P3 + * + * For the HDR image, only YCBCR_P010 image format is supported, and the following + * color spaces are supported: + * ColorSpace.Named.BT2020_HLG, + * ColorSpace.Named.BT2020_PQ + * + * @param sdr The SDR image, only ImageFormat.YUV_420_888 is supported. + * @param quality Hint to the compressor, 0-100. 0 meaning compress for + * small size, 100 meaning compress for max quality. + * @param stream OutputStream to write the compressed data. + * @return True if the compression is successful. + * @throws IllegalArgumentException if input images are invalid; quality is not within [0, + * 100]; or stream is null. + */ + public boolean compressToJpegR(@NonNull YuvImage sdr, int quality, + @NonNull OutputStream stream) { + byte[] emptyExif = new byte[0]; + return compressToJpegR(sdr, quality, stream, emptyExif); + } + /** * Compress the HDR image into JPEG/R format. * @@ -263,12 +296,14 @@ public class YuvImage { * @param quality Hint to the compressor, 0-100. 0 meaning compress for * small size, 100 meaning compress for max quality. * @param stream OutputStream to write the compressed data. + * @param exif Exchangeable image file format. * @return True if the compression is successful. * @throws IllegalArgumentException if input images are invalid; quality is not within [0, * 100]; or stream is null. */ + @FlaggedApi(Flags.FLAG_YUV_IMAGE_COMPRESS_TO_ULTRA_HDR) public boolean compressToJpegR(@NonNull YuvImage sdr, int quality, - @NonNull OutputStream stream) { + @NonNull OutputStream stream, @NonNull byte[] exif) { if (sdr == null) { throw new IllegalArgumentException("SDR input cannot be null"); } @@ -304,7 +339,8 @@ public class YuvImage { return nativeCompressToJpegR(mData, mColorSpace.getDataSpace(), sdr.getYuvData(), sdr.getColorSpace().getDataSpace(), mWidth, mHeight, quality, stream, - new byte[WORKING_COMPRESS_STORAGE]); + new byte[WORKING_COMPRESS_STORAGE], exif, + mStrides, sdr.getStrides()); } @@ -416,5 +452,6 @@ public class YuvImage { private static native boolean nativeCompressToJpegR(byte[] hdr, int hdrColorSpaceId, byte[] sdr, int sdrColorSpaceId, int width, int height, int quality, - OutputStream stream, byte[] tempStorage); + OutputStream stream, byte[] tempStorage, byte[] exif, + int[] hdrStrides, int[] sdrStrides); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 3e113276027e..e74e578dc213 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -788,7 +788,7 @@ public class BubbleController implements ConfigurationChangeListener, mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> { if (!windowInsets.equals(mWindowInsets) && mLayerView != null) { mWindowInsets = windowInsets; - mBubblePositioner.update(); + mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager)); mLayerView.onDisplaySizeChanged(); } return windowInsets; @@ -798,7 +798,7 @@ public class BubbleController implements ConfigurationChangeListener, mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> { if (!windowInsets.equals(mWindowInsets) && mStackView != null) { mWindowInsets = windowInsets; - mBubblePositioner.update(); + mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager)); mStackView.onDisplaySizeChanged(); } return windowInsets; @@ -980,7 +980,7 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void onConfigurationChanged(Configuration newConfig) { if (mBubblePositioner != null) { - mBubblePositioner.update(); + mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager)); } if (mStackView != null && newConfig != null) { if (newConfig.densityDpi != mDensityDpi diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 09ae84a50328..1efd9df3a1d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -16,10 +16,7 @@ package com.android.wm.shell.bubbles; -import static android.view.View.LAYOUT_DIRECTION_RTL; - import android.content.Context; -import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Point; @@ -28,9 +25,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.Surface; -import android.view.WindowInsets; import android.view.WindowManager; -import android.view.WindowMetrics; import androidx.annotation.VisibleForTesting; @@ -68,15 +63,12 @@ public class BubblePositioner { private static final float EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT = 0.4f; private Context mContext; - private WindowManager mWindowManager; + private DeviceConfig mDeviceConfig; private Rect mScreenRect; private @Surface.Rotation int mRotation = Surface.ROTATION_0; private Insets mInsets; private boolean mImeVisible; private int mImeHeight; - private boolean mIsLargeScreen; - private boolean mIsSmallTablet; - private Rect mPositionRect; private int mDefaultMaxBubbles; private int mMaxBubbles; @@ -110,44 +102,27 @@ public class BubblePositioner { public BubblePositioner(Context context, WindowManager windowManager) { mContext = context; - mWindowManager = windowManager; - update(); + mDeviceConfig = DeviceConfig.create(context, windowManager); + update(mDeviceConfig); } /** * Available space and inset information. Call this when config changes * occur or when added to a window. */ - public void update() { - WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); - if (windowMetrics == null) { - return; - } - WindowInsets metricInsets = windowMetrics.getWindowInsets(); - Insets insets = metricInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars() - | WindowInsets.Type.statusBars() - | WindowInsets.Type.displayCutout()); - - final Rect bounds = windowMetrics.getBounds(); - Configuration config = mContext.getResources().getConfiguration(); - mIsLargeScreen = config.smallestScreenWidthDp >= 600; - if (mIsLargeScreen) { - float largestEdgeDp = Math.max(config.screenWidthDp, config.screenHeightDp); - mIsSmallTablet = largestEdgeDp < 960; - } else { - mIsSmallTablet = false; - } + public void update(DeviceConfig deviceConfig) { + mDeviceConfig = deviceConfig; if (BubbleDebugConfig.DEBUG_POSITIONER) { Log.w(TAG, "update positioner:" + " rotation: " + mRotation - + " insets: " + insets - + " isLargeScreen: " + mIsLargeScreen - + " isSmallTablet: " + mIsSmallTablet + + " insets: " + deviceConfig.getInsets() + + " isLargeScreen: " + deviceConfig.isLargeScreen() + + " isSmallTablet: " + deviceConfig.isSmallTablet() + " showingInBubbleBar: " + mShowingInBubbleBar - + " bounds: " + bounds); + + " bounds: " + deviceConfig.getWindowBounds()); } - updateInternal(mRotation, insets, bounds); + updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds()); } @VisibleForTesting @@ -175,15 +150,15 @@ public class BubblePositioner { mExpandedViewLargeScreenWidth = isLandscape() ? (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT) : (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_PORTRAIT_WIDTH_PERCENT); - } else if (mIsSmallTablet) { + } else if (mDeviceConfig.isSmallTablet()) { mExpandedViewLargeScreenWidth = (int) (bounds.width() * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT); } else { mExpandedViewLargeScreenWidth = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width); } - if (mIsLargeScreen) { - if (mIsSmallTablet) { + if (mDeviceConfig.isLargeScreen()) { + if (mDeviceConfig.isSmallTablet()) { final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2; mExpandedViewLargeScreenInsetClosestEdge = centeredInset; mExpandedViewLargeScreenInsetFurthestEdge = centeredInset; @@ -264,13 +239,12 @@ public class BubblePositioner { /** @return whether the device is in landscape orientation. */ public boolean isLandscape() { - return mContext.getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; + return mDeviceConfig.isLandscape(); } /** @return whether the screen is considered large. */ public boolean isLargeScreen() { - return mIsLargeScreen; + return mDeviceConfig.isLargeScreen(); } /** @@ -281,7 +255,7 @@ public class BubblePositioner { * to the left or right side. */ public boolean showBubblesVertically() { - return isLandscape() || mIsLargeScreen; + return isLandscape() || mDeviceConfig.isLargeScreen(); } /** Size of the bubble. */ @@ -334,7 +308,7 @@ public class BubblePositioner { } private int getExpandedViewLargeScreenInsetFurthestEdge(boolean isOverflow) { - if (isOverflow && mIsLargeScreen) { + if (isOverflow && mDeviceConfig.isLargeScreen()) { return mScreenRect.width() - mExpandedViewLargeScreenInsetClosestEdge - mOverflowWidth; @@ -358,7 +332,7 @@ public class BubblePositioner { final int pointerTotalHeight = getPointerSize(); final int expandedViewLargeScreenInsetFurthestEdge = getExpandedViewLargeScreenInsetFurthestEdge(isOverflow); - if (mIsLargeScreen) { + if (mDeviceConfig.isLargeScreen()) { // Note: // If we're in portrait OR if we're a small tablet, then the two insets values will // be equal. If we're landscape and a large tablet, the two values will be different. @@ -439,12 +413,12 @@ public class BubblePositioner { */ public float getExpandedViewHeight(BubbleViewProvider bubble) { boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey()); - if (isOverflow && showBubblesVertically() && !mIsLargeScreen) { + if (isOverflow && showBubblesVertically() && !mDeviceConfig.isLargeScreen()) { // overflow in landscape on phone is max return MAX_HEIGHT; } - if (mIsLargeScreen && !mIsSmallTablet && !isOverflow) { + if (mDeviceConfig.isLargeScreen() && !mDeviceConfig.isSmallTablet() && !isOverflow) { // the expanded view height on large tablets is calculated based on the shortest screen // size and is the same in both portrait and landscape int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom); @@ -529,11 +503,9 @@ public class BubblePositioner { */ public PointF getExpandedBubbleXY(int index, BubbleStackView.StackViewState state) { boolean showBubblesVertically = showBubblesVertically(); - boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection() - == LAYOUT_DIRECTION_RTL; int onScreenIndex; - if (showBubblesVertically || !isRtl) { + if (showBubblesVertically || !mDeviceConfig.isRtl()) { onScreenIndex = index; } else { // If bubbles are shown horizontally, check if RTL language is used. @@ -554,10 +526,10 @@ public class BubblePositioner { if (showBubblesVertically) { int inset = mExpandedViewLargeScreenInsetClosestEdge; y = rowStart + positionInRow; - int left = mIsLargeScreen + int left = mDeviceConfig.isLargeScreen() ? inset - mExpandedViewPadding - mBubbleSize : mPositionRect.left; - int right = mIsLargeScreen + int right = mDeviceConfig.isLargeScreen() ? mPositionRect.right - inset + mExpandedViewPadding : mPositionRect.right - mBubbleSize; x = state.onLeft @@ -693,13 +665,10 @@ public class BubblePositioner { * @param isAppBubble whether this start position is for an app bubble or not. */ public PointF getDefaultStartPosition(boolean isAppBubble) { - final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection(); // Normal bubbles start on the left if we're in LTR, right otherwise. // TODO (b/294284894): update language around "app bubble" here // App bubbles start on the right in RTL, left otherwise. - final boolean startOnLeft = isAppBubble - ? layoutDirection == LAYOUT_DIRECTION_RTL - : layoutDirection != LAYOUT_DIRECTION_RTL; + final boolean startOnLeft = isAppBubble ? mDeviceConfig.isRtl() : !mDeviceConfig.isRtl(); return getStartPosition(startOnLeft ? StackPinnedEdge.LEFT : StackPinnedEdge.RIGHT); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 2cee675e83be..8f904c42d247 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -61,6 +61,7 @@ import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; +import android.view.WindowManager; import android.view.WindowManagerPolicyConstants; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -1001,7 +1002,8 @@ public class BubbleStackView extends FrameLayout mOrientationChangedListener = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - mPositioner.update(); + mPositioner.update(DeviceConfig.create(mContext, mContext.getSystemService( + WindowManager.class))); onDisplaySizeChanged(); mExpandedAnimationController.updateResources(); mStackAnimationController.updateResources(); @@ -1522,7 +1524,8 @@ public class BubbleStackView extends FrameLayout @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mPositioner.update(); + WindowManager windowManager = mContext.getSystemService(WindowManager.class); + mPositioner.update(DeviceConfig.create(mContext, Objects.requireNonNull(windowManager))); getViewTreeObserver().addOnComputeInternalInsetsListener(this); getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt new file mode 100644 index 000000000000..929330918174 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles + +import android.content.Context +import android.content.res.Configuration +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.graphics.Insets +import android.graphics.Rect +import android.view.View.LAYOUT_DIRECTION_RTL +import android.view.WindowInsets +import android.view.WindowManager +import kotlin.math.max + +/** Contains device configuration used for positioning bubbles on the screen. */ +data class DeviceConfig( + val isLargeScreen: Boolean, + val isSmallTablet: Boolean, + val isLandscape: Boolean, + val isRtl: Boolean, + val windowBounds: Rect, + val insets: Insets +) { + companion object { + + private const val LARGE_SCREEN_MIN_EDGE_DP = 600 + private const val SMALL_TABLET_MAX_EDGE_DP = 960 + + @JvmStatic + fun create(context: Context, windowManager: WindowManager): DeviceConfig { + val windowMetrics = windowManager.currentWindowMetrics + val metricInsets = windowMetrics.windowInsets + val insets = metricInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars() + or WindowInsets.Type.statusBars() + or WindowInsets.Type.displayCutout()) + val windowBounds = windowMetrics.bounds + val config: Configuration = context.resources.configuration + val isLargeScreen = config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP + val largestEdgeDp = max(config.screenWidthDp, config.screenHeightDp) + val isSmallTablet = isLargeScreen && largestEdgeDp < SMALL_TABLET_MAX_EDGE_DP + val isLandscape = context.resources.configuration.orientation == ORIENTATION_LANDSCAPE + val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL + return DeviceConfig( + isLargeScreen = isLargeScreen, + isSmallTablet = isSmallTablet, + isLandscape = isLandscape, + isRtl = isRtl, + windowBounds = windowBounds, + insets = insets + ) + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index e788341df5f8..92cb436528cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -28,13 +28,16 @@ import android.graphics.drawable.ColorDrawable; import android.view.TouchDelegate; import android.view.View; import android.view.ViewTreeObserver; +import android.view.WindowManager; import android.widget.FrameLayout; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; +import com.android.wm.shell.bubbles.DeviceConfig; +import java.util.Objects; import java.util.function.Consumer; import kotlin.Unit; @@ -104,7 +107,8 @@ public class BubbleBarLayerView extends FrameLayout @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mPositioner.update(); + WindowManager windowManager = mContext.getSystemService(WindowManager.class); + mPositioner.update(DeviceConfig.create(mContext, Objects.requireNonNull(windowManager))); getViewTreeObserver().addOnComputeInternalInsetsListener(this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 4a9ea6fed73f..144555dd70c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -21,7 +21,6 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN -import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context @@ -321,24 +320,10 @@ class DesktopTasksController( } /** Move a task with given `taskId` to fullscreen */ - fun moveToFullscreen(taskId: Int) { - shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) } - } - - /** Move a task to fullscreen */ - fun moveToFullscreen(task: RunningTaskInfo) { - KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: moveToFullscreen taskId=%d", - task.taskId - ) - - val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) - } else { - shellTaskOrganizer.applyTransaction(wct) + fun moveToFullscreen(taskId: Int, windowDecor: DesktopModeWindowDecoration) { + shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> + windowDecor.incrementRelayoutBlock() + moveToFullscreenWithAnimation(task, task.positionInParent) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 75d27d99b9ac..95d7ad5c416f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -25,12 +25,14 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP import com.android.wm.shell.transition.Transitions.TransitionHandler +import com.android.wm.shell.util.KtProtoLog import com.android.wm.shell.util.TransitionUtil import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.MoveToDesktopAnimator @@ -68,6 +70,10 @@ class DragToDesktopTransitionHandler( private var splitScreenController: SplitScreenController? = null private var transitionState: TransitionState? = null + /** Whether a drag-to-desktop transition is in progress. */ + val inProgress: Boolean + get() = transitionState != null + /** Sets a listener to receive callback about events during the transition animation. */ fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) { dragToDesktopStateListener = listener @@ -92,19 +98,22 @@ class DragToDesktopTransitionHandler( dragToDesktopAnimator: MoveToDesktopAnimator, windowDecoration: DesktopModeWindowDecoration ) { - if (transitionState != null) { + if (inProgress) { error("A drag to desktop is already in progress") } val options = ActivityOptions.makeBasic().apply { setTransientLaunch() setSourceInfo(SourceInfo.TYPE_DESKTOP_ANIMATION, SystemClock.uptimeMillis()) + pendingIntentCreatorBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED } val pendingIntent = PendingIntent.getActivity( context, 0 /* requestCode */, launchHomeIntent, - FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT + FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT, + options.toBundle() ) val wct = WindowContainerTransaction() wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle()) @@ -135,6 +144,12 @@ class DragToDesktopTransitionHandler( * inside the desktop drop zone. */ fun finishDragToDesktopTransition(wct: WindowContainerTransaction) { + if (requireTransitionState().startAborted) { + // Don't attempt to complete the drag-to-desktop since the start transition didn't + // succeed as expected. Just reset the state as if nothing happened. + clearState() + return + } transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this) } @@ -147,6 +162,12 @@ class DragToDesktopTransitionHandler( */ fun cancelDragToDesktopTransition() { val state = requireTransitionState() + if (state.startAborted) { + // Don't attempt to cancel the drag-to-desktop since the start transition didn't + // succeed as expected. Just reset the state as if nothing happened. + clearState() + return + } state.cancelled = true if (state.draggedTaskChange != null) { // Regular case, transient launch of Home happened as is waiting for the cancel @@ -409,6 +430,21 @@ class DragToDesktopTransitionHandler( return null } + override fun onTransitionConsumed( + transition: IBinder, + aborted: Boolean, + finishTransaction: SurfaceControl.Transaction? + ) { + val state = transitionState ?: return + if (aborted && state.startTransitionToken == transition) { + KtProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DragToDesktop: onTransitionConsumed() start transition aborted" + ) + state.startAborted = true + } + } + private fun isHomeChange(change: Change): Boolean { return change.taskInfo?.activityType == ACTIVITY_TYPE_HOME } @@ -508,6 +544,7 @@ class DragToDesktopTransitionHandler( abstract var homeToken: WindowContainerToken? abstract var draggedTaskChange: Change? abstract var cancelled: Boolean + abstract var startAborted: Boolean data class FromFullscreen( override val draggedTaskId: Int, @@ -520,6 +557,7 @@ class DragToDesktopTransitionHandler( override var homeToken: WindowContainerToken? = null, override var draggedTaskChange: Change? = null, override var cancelled: Boolean = false, + override var startAborted: Boolean = false, ) : TransitionState() data class FromSplit( override val draggedTaskId: Int, @@ -532,6 +570,7 @@ class DragToDesktopTransitionHandler( override var homeToken: WindowContainerToken? = null, override var draggedTaskChange: Change? = null, override var cancelled: Boolean = false, + override var startAborted: Boolean = false, var splitRootChange: Change? = null, ) : TransitionState() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index dd6ca8da56eb..03006f920072 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -428,7 +428,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (isTaskInSplitScreen(mTaskId)) { mSplitScreenController.moveTaskToFullscreen(mTaskId); } else { - mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId)); + mDesktopTasksController.ifPresent(c -> + c.moveToFullscreen(mTaskId, mWindowDecorByTaskId.get(mTaskId))); } } else if (id == R.id.split_screen_button) { decoration.closeHandleMenu(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index e1d177af2331..6ec91e0e28dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -548,8 +548,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ private PointF offsetCaptionLocation(MotionEvent ev) { final PointF result = new PointF(ev.getX(), ev.getY()); - final Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId) - .positionInParent; + final ActivityManager.RunningTaskInfo taskInfo = + mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId); + if (taskInfo == null) return result; + final Point positionInParent = taskInfo.positionInParent; result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY); result.offset(-positionInParent.x, -positionInParent.y); return result; diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt index 744e8c2eb06f..181474fa0590 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt @@ -57,25 +57,18 @@ class LetterboxRule( resetLetterboxStyle() _letterboxStyle = mapLetterboxStyle() val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled") - var hasLetterboxEducationStateChanged = false if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) { - hasLetterboxEducationStateChanged = true execAdb("wm set-letterbox-style --isEducationEnabled " + withLetterboxEducationEnabled) } - return try { - object : Statement() { - @Throws(Throwable::class) - override fun evaluate() { + return object : Statement() { + @Throws(Throwable::class) + override fun evaluate() { + try { base!!.evaluate() + } finally { + resetLetterboxStyle() } } - } finally { - if (hasLetterboxEducationStateChanged) { - execAdb( - "wm set-letterbox-style --isEducationEnabled " + isLetterboxEducationEnabled - ) - } - resetLetterboxStyle() } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml index 89ecc29d977b..6429b00a2a58 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml @@ -76,6 +76,17 @@ value="appops set com.android.shell android:mock_location deny"/> </target_preparer> + <!-- Use app crawler to log into Netflix --> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" + value="am start -n com.netflix.mediaclient/com.netflix.mediaclient.ui.login.LoginActivity"/> + </target_preparer> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="set-option" value="package-name:com.netflix.mediaclient"/> + <option name="set-option" value="ui-automator-mode:true"/> + <option name="class" value="com.android.csuite.tests.AppCrawlTest" /> + </test> + <!-- Needed for pushing the trace config file --> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml index fdda5974d1f9..05f937ab6795 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml @@ -95,6 +95,8 @@ <option name="pull-pattern-keys" value="perfetto_file_path"/> <option name="directory-keys" value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell.flicker.service/files"/> <option name="collect-on-run-ended-only" value="true"/> <option name="clean-up" value="true"/> </metrics_collector> diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto index b55f4ecdb6a4..67316d2d7c0f 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto @@ -63,6 +63,7 @@ data_sources: { atrace_categories: "sched_process_exit" atrace_apps: "com.android.server.wm.flicker.testapp" atrace_apps: "com.android.systemui" + atrace_apps: "com.android.wm.shell.flicker.service" atrace_apps: "com.android.wm.shell.flicker.splitscreen" atrace_apps: "com.google.android.apps.nexuslauncher" } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 26c73946c1c6..4bca96b187b5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -192,7 +192,7 @@ public class BubbleDataTest extends ShellTestCase { mMainExecutor); mPositioner = new TestableBubblePositioner(mContext, - mock(WindowManager.class)); + mContext.getSystemService(WindowManager.class)); mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController, mMainExecutor); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java index cb29a21e2f5b..f5b0174642d1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java @@ -53,7 +53,8 @@ public class BubbleOverflowTest extends ShellTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mPositioner = new TestableBubblePositioner(mContext, mock(WindowManager.class)); + mPositioner = new TestableBubblePositioner(mContext, + mContext.getSystemService(WindowManager.class)); when(mBubbleController.getPositioner()).thenReturn(mPositioner); when(mBubbleController.getStackView()).thenReturn(mock(BubbleStackView.class)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java index 287a97c9b5b0..835ebe2206ad 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java @@ -16,33 +16,17 @@ package com.android.wm.shell.bubbles; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.view.View.LAYOUT_DIRECTION_LTR; -import static android.view.View.LAYOUT_DIRECTION_RTL; - import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - import android.content.Intent; -import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.UserHandle; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableResources; -import android.util.DisplayMetrics; -import android.view.WindowInsets; import android.view.WindowManager; -import android.view.WindowMetrics; import androidx.test.filters.SmallTest; @@ -52,36 +36,20 @@ import com.android.wm.shell.ShellTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; /** * Tests operations and the resulting state managed by {@link BubblePositioner}. */ @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) public class BubblePositionerTest extends ShellTestCase { - private static final int MIN_WIDTH_FOR_TABLET = 600; - private BubblePositioner mPositioner; - private Configuration mConfiguration; - - @Mock - private WindowManager mWindowManager; - @Mock - private WindowMetrics mWindowMetrics; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - - mConfiguration = spy(new Configuration()); - TestableResources testableResources = mContext.getOrCreateTestableResources(); - testableResources.overrideConfiguration(mConfiguration); - - mPositioner = new BubblePositioner(mContext, mWindowManager); + WindowManager windowManager = mContext.getSystemService(WindowManager.class); + mPositioner = new BubblePositioner(mContext, windowManager); } @Test @@ -91,11 +59,11 @@ public class BubblePositionerTest extends ShellTestCase { Rect availableRect = new Rect(screenBounds); availableRect.inset(insets); - new WindowManagerConfig() + DeviceConfig deviceConfig = new ConfigBuilder() .setInsets(insets) .setScreenBounds(screenBounds) - .setUpConfig(); - mPositioner.update(); + .build(); + mPositioner.update(deviceConfig); assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect); assertThat(mPositioner.isLandscape()).isFalse(); @@ -105,16 +73,16 @@ public class BubblePositionerTest extends ShellTestCase { @Test public void testShowBubblesVertically_phonePortrait() { - new WindowManagerConfig().setOrientation(ORIENTATION_PORTRAIT).setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().build(); + mPositioner.update(deviceConfig); assertThat(mPositioner.showBubblesVertically()).isFalse(); } @Test public void testShowBubblesVertically_phoneLandscape() { - new WindowManagerConfig().setOrientation(ORIENTATION_LANDSCAPE).setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().setLandscape().build(); + mPositioner.update(deviceConfig); assertThat(mPositioner.isLandscape()).isTrue(); assertThat(mPositioner.showBubblesVertically()).isTrue(); @@ -122,8 +90,8 @@ public class BubblePositionerTest extends ShellTestCase { @Test public void testShowBubblesVertically_tablet() { - new WindowManagerConfig().setLargeScreen().setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build(); + mPositioner.update(deviceConfig); assertThat(mPositioner.showBubblesVertically()).isTrue(); } @@ -131,8 +99,8 @@ public class BubblePositionerTest extends ShellTestCase { /** If a resting position hasn't been set, calling it will return the default position. */ @Test public void testGetRestingPosition_returnsDefaultPosition() { - new WindowManagerConfig().setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().build(); + mPositioner.update(deviceConfig); PointF restingPosition = mPositioner.getRestingPosition(); PointF defaultPosition = mPositioner.getDefaultStartPosition(); @@ -143,8 +111,8 @@ public class BubblePositionerTest extends ShellTestCase { /** If a resting position has been set, it'll return that instead of the default position. */ @Test public void testGetRestingPosition_returnsRestingPosition() { - new WindowManagerConfig().setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().build(); + mPositioner.update(deviceConfig); PointF restingPosition = new PointF(100, 100); mPositioner.setRestingPosition(restingPosition); @@ -155,8 +123,8 @@ public class BubblePositionerTest extends ShellTestCase { /** Test that the default resting position on phone is in upper left. */ @Test public void testGetRestingPosition_bubble_onPhone() { - new WindowManagerConfig().setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().build(); + mPositioner.update(deviceConfig); RectF allowableStackRegion = mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); @@ -168,8 +136,8 @@ public class BubblePositionerTest extends ShellTestCase { @Test public void testGetRestingPosition_bubble_onPhone_RTL() { - new WindowManagerConfig().setLayoutDirection(LAYOUT_DIRECTION_RTL).setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().setRtl().build(); + mPositioner.update(deviceConfig); RectF allowableStackRegion = mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); @@ -182,8 +150,8 @@ public class BubblePositionerTest extends ShellTestCase { /** Test that the default resting position on tablet is middle left. */ @Test public void testGetRestingPosition_chatBubble_onTablet() { - new WindowManagerConfig().setLargeScreen().setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build(); + mPositioner.update(deviceConfig); RectF allowableStackRegion = mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); @@ -195,9 +163,8 @@ public class BubblePositionerTest extends ShellTestCase { @Test public void testGetRestingPosition_chatBubble_onTablet_RTL() { - new WindowManagerConfig().setLargeScreen().setLayoutDirection( - LAYOUT_DIRECTION_RTL).setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build(); + mPositioner.update(deviceConfig); RectF allowableStackRegion = mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); @@ -210,8 +177,8 @@ public class BubblePositionerTest extends ShellTestCase { /** Test that the default resting position on tablet is middle right. */ @Test public void testGetDefaultPosition_appBubble_onTablet() { - new WindowManagerConfig().setLargeScreen().setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build(); + mPositioner.update(deviceConfig); RectF allowableStackRegion = mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); @@ -223,9 +190,8 @@ public class BubblePositionerTest extends ShellTestCase { @Test public void testGetRestingPosition_appBubble_onTablet_RTL() { - new WindowManagerConfig().setLargeScreen().setLayoutDirection( - LAYOUT_DIRECTION_RTL).setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build(); + mPositioner.update(deviceConfig); RectF allowableStackRegion = mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); @@ -237,9 +203,8 @@ public class BubblePositionerTest extends ShellTestCase { @Test public void testHasUserModifiedDefaultPosition_false() { - new WindowManagerConfig().setLargeScreen().setLayoutDirection( - LAYOUT_DIRECTION_RTL).setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build(); + mPositioner.update(deviceConfig); assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); @@ -250,9 +215,8 @@ public class BubblePositionerTest extends ShellTestCase { @Test public void testHasUserModifiedDefaultPosition_true() { - new WindowManagerConfig().setLargeScreen().setLayoutDirection( - LAYOUT_DIRECTION_RTL).setUpConfig(); - mPositioner.update(); + DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build(); + mPositioner.update(deviceConfig); assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); @@ -266,12 +230,12 @@ public class BubblePositionerTest extends ShellTestCase { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); - new WindowManagerConfig() + DeviceConfig deviceConfig = new ConfigBuilder() .setLargeScreen() .setInsets(insets) .setScreenBounds(screenBounds) - .setUpConfig(); - mPositioner.update(); + .build(); + mPositioner.update(deviceConfig); Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); @@ -311,58 +275,47 @@ public class BubblePositionerTest extends ShellTestCase { * Sets up window manager to return config values based on what you need for the test. * By default it sets up a portrait phone without any insets. */ - private class WindowManagerConfig { + private static class ConfigBuilder { private Rect mScreenBounds = new Rect(0, 0, 1000, 2000); private boolean mIsLargeScreen = false; - private int mOrientation = ORIENTATION_PORTRAIT; - private int mLayoutDirection = LAYOUT_DIRECTION_LTR; + private boolean mIsSmallTablet = false; + private boolean mIsLandscape = false; + private boolean mIsRtl = false; private Insets mInsets = Insets.of(0, 0, 0, 0); - public WindowManagerConfig setScreenBounds(Rect screenBounds) { + public ConfigBuilder setScreenBounds(Rect screenBounds) { mScreenBounds = screenBounds; return this; } - public WindowManagerConfig setLargeScreen() { + public ConfigBuilder setLargeScreen() { mIsLargeScreen = true; return this; } - public WindowManagerConfig setOrientation(int orientation) { - mOrientation = orientation; + public ConfigBuilder setSmallTablet() { + mIsSmallTablet = true; return this; } - public WindowManagerConfig setLayoutDirection(int layoutDirection) { - mLayoutDirection = layoutDirection; + public ConfigBuilder setLandscape() { + mIsLandscape = true; return this; } - public WindowManagerConfig setInsets(Insets insets) { - mInsets = insets; + public ConfigBuilder setRtl() { + mIsRtl = true; return this; } - public void setUpConfig() { - mConfiguration.smallestScreenWidthDp = mIsLargeScreen - ? MIN_WIDTH_FOR_TABLET - : MIN_WIDTH_FOR_TABLET - 1; - mConfiguration.orientation = mOrientation; - mConfiguration.screenWidthDp = pxToDp(mScreenBounds.width()); - mConfiguration.screenHeightDp = pxToDp(mScreenBounds.height()); - - when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection); - WindowInsets windowInsets = mock(WindowInsets.class); - when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(mInsets); - when(mWindowMetrics.getWindowInsets()).thenReturn(windowInsets); - when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds); - when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); + public ConfigBuilder setInsets(Insets insets) { + mInsets = insets; + return this; } - private int pxToDp(float px) { - int dpi = mContext.getResources().getDisplayMetrics().densityDpi; - float dp = px / ((float) dpi / DisplayMetrics.DENSITY_DEFAULT); - return (int) dp; + private DeviceConfig build() { + return new DeviceConfig(mIsLargeScreen, mIsSmallTablet, mIsLandscape, mIsRtl, + mScreenBounds, mInsets); } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java index 44ff35466ae2..c4b9c9ba43f1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java @@ -55,8 +55,6 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase { private BubblesNavBarMotionEventHandler mMotionEventHandler; @Mock - private WindowManager mWindowManager; - @Mock private Runnable mInterceptTouchRunnable; @Mock private MotionEventListener mMotionEventListener; @@ -66,7 +64,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase { public void setUp() { MockitoAnnotations.initMocks(this); TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(), - mWindowManager); + getContext().getSystemService(WindowManager.class)); mMotionEventHandler = new BubblesNavBarMotionEventHandler(getContext(), positioner, mInterceptTouchRunnable, mMotionEventListener); mMotionEventTime = SystemClock.uptimeMillis(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java index 335222e98c6c..6403e794a33e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java @@ -66,7 +66,8 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC public void setUp() throws Exception { super.setUp(); - mPositioner = new BubblePositioner(getContext(), mock(WindowManager.class)); + mPositioner = new BubblePositioner(getContext(), + getContext().getSystemService(WindowManager.class)); mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT, Insets.of(0, 0, 0, 0), new Rect(0, 0, mDisplayWidth, mDisplayHeight)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java index 991913afbb90..f6609872852f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java @@ -50,9 +50,6 @@ public class ExpandedViewAnimationControllerTest extends ShellTestCase { private ExpandedViewAnimationController mController; @Mock - private WindowManager mWindowManager; - - @Mock private BubbleExpandedView mMockExpandedView; @Before @@ -60,7 +57,7 @@ public class ExpandedViewAnimationControllerTest extends ShellTestCase { MockitoAnnotations.initMocks(this); TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(), - mWindowManager); + getContext().getSystemService(WindowManager.class)); mController = new ExpandedViewAnimationControllerImpl(getContext(), positioner); mController.setExpandedView(mMockExpandedView); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java index 31fafcaab7cc..0c22908be9eb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java @@ -313,7 +313,8 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase bubbleCountSupplier, onBubbleAnimatedOutAction, onStackAnimationFinished, - new TestableBubblePositioner(mContext, mock(WindowManager.class))); + new TestableBubblePositioner(mContext, + mContext.getSystemService(WindowManager.class))); } @Override diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index fde6acb9bfe5..94c862bd7a4f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -63,6 +63,7 @@ import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS +import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE import com.android.wm.shell.transition.Transitions.TransitionHandler import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.google.common.truth.Truth.assertThat @@ -392,8 +393,8 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() { val task = setUpFreeformTask() task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN - controller.moveToFullscreen(task) - val wct = getLatestWct(type = TRANSIT_CHANGE) + controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration) + val wct = getLatestExitDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) } @@ -402,15 +403,15 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() { val task = setUpFreeformTask() task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM - controller.moveToFullscreen(task) - val wct = getLatestWct(type = TRANSIT_CHANGE) + controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration) + val wct = getLatestExitDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) } @Test fun moveToFullscreen_nonExistentTask_doesNothing() { - controller.moveToFullscreen(999) + controller.moveToFullscreen(999, desktopModeWindowDecoration) verifyWCTNotExecuted() } @@ -419,9 +420,9 @@ class DesktopTasksControllerTest : ShellTestCase() { val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY) - controller.moveToFullscreen(taskDefaultDisplay) + controller.moveToFullscreen(taskDefaultDisplay.taskId, desktopModeWindowDecoration) - with(getLatestWct(type = TRANSIT_CHANGE)) { + with(getLatestExitDesktopWct()) { assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder()) assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder()) } @@ -808,6 +809,17 @@ class DesktopTasksControllerTest : ShellTestCase() { return arg.value } + private fun getLatestExitDesktopWct(): WindowContainerTransaction { + val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + if (ENABLE_SHELL_TRANSITIONS) { + verify(exitDesktopTransitionHandler) + .startTransition(eq(TRANSIT_EXIT_DESKTOP_MODE), arg.capture(), any(), any()) + } else { + verify(shellTaskOrganizer).applyTransaction(arg.capture()) + } + return arg.value + } + private fun verifyWCTNotExecuted() { if (ENABLE_SHELL_TRANSITIONS) { verify(transitions, never()).startTransition(anyInt(), any(), isNull()) diff --git a/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 b355ab0bf311..3bc90ade898e 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 @@ -13,6 +13,7 @@ import android.testing.TestableLooper.RunWithLooper import android.view.SurfaceControl import android.window.TransitionInfo import android.window.TransitionInfo.FLAG_IS_WALLPAPER +import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase @@ -20,9 +21,11 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP +import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import java.util.function.Supplier +import junit.framework.Assert.assertFalse import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -113,6 +116,40 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { } @Test + fun startDragToDesktop_aborted_finishDropped() { + val task = createTask() + val dragAnimator = mock<MoveToDesktopAnimator>() + // Simulate transition is started. + val transition = startDragToDesktopTransition(task, dragAnimator) + // But the transition was aborted. + handler.onTransitionConsumed(transition, aborted = true, mock()) + + // Attempt to finish the failed drag start. + handler.finishDragToDesktopTransition(WindowContainerTransaction()) + + // Should not be attempted and state should be reset. + verify(transitions, never()) + .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any()) + assertFalse(handler.inProgress) + } + + @Test + fun startDragToDesktop_aborted_cancelDropped() { + val task = createTask() + val dragAnimator = mock<MoveToDesktopAnimator>() + // Simulate transition is started. + val transition = startDragToDesktopTransition(task, dragAnimator) + // But the transition was aborted. + handler.onTransitionConsumed(transition, aborted = true, mock()) + + // Attempt to finish the failed drag start. + handler.cancelDragToDesktopTransition() + + // Should not be attempted and state should be reset. + assertFalse(handler.inProgress) + } + + @Test fun cancelDragToDesktop_startWasReady_cancel() { val task = createTask() val dragAnimator = mock<MoveToDesktopAnimator>() diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp index cd4fae86aa52..b667daf9c850 100644 --- a/libs/hwui/CanvasTransform.cpp +++ b/libs/hwui/CanvasTransform.cpp @@ -80,6 +80,19 @@ SkColor transformColorInverse(ColorTransform transform, SkColor color) { static void applyColorTransform(ColorTransform transform, SkPaint& paint) { if (transform == ColorTransform::None) return; + if (transform == ColorTransform::Invert) { + auto filter = SkHighContrastFilter::Make( + {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness, + /* contrast= */ 0.0f}); + + if (paint.getColorFilter()) { + paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter())); + } else { + paint.setColorFilter(filter); + } + return; + } + SkColor newColor = transformColor(transform, paint.getColor()); paint.setColor(newColor); diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h index 291f4cf7193b..288dca4de5c1 100644 --- a/libs/hwui/CanvasTransform.h +++ b/libs/hwui/CanvasTransform.h @@ -29,12 +29,15 @@ enum class UsageHint { Unknown = 0, Background = 1, Foreground = 2, + // Contains foreground (usually text), like a button or chip + Container = 3 }; enum class ColorTransform { None, Light, Dark, + Invert }; // True if the paint was modified, false otherwise diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 8c180da9c84f..b1c5bf49ede5 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -145,6 +145,8 @@ public: return mImpl && mImpl->hasText(); } + [[nodiscard]] bool hasFill() const { return mImpl && mImpl->hasFill(); } + void applyColorTransform(ColorTransform transform) { if (mImpl) { mImpl->applyColorTransform(transform); diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index ff0d8d74831c..3b694c5d399b 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -718,6 +718,27 @@ static constexpr inline bool is_power_of_two(int value) { return (value & (value - 1)) == 0; } +template <typename T> +constexpr bool doesPaintHaveFill(T& paint) { + using T1 = std::remove_cv_t<T>; + if constexpr (std::is_same_v<T1, SkPaint>) { + return paint.getStyle() != SkPaint::Style::kStroke_Style; + } else if constexpr (std::is_same_v<T1, SkPaint&>) { + return paint.getStyle() != SkPaint::Style::kStroke_Style; + } else if constexpr (std::is_same_v<T1, SkPaint*>) { + return paint && paint->getStyle() != SkPaint::Style::kStroke_Style; + } else if constexpr (std::is_same_v<T1, const SkPaint*>) { + return paint && paint->getStyle() != SkPaint::Style::kStroke_Style; + } + + return false; +} + +template <typename... Args> +constexpr bool hasPaintWithFill(Args&&... args) { + return (... || doesPaintHaveFill(args)); +} + template <typename T, typename... Args> void* DisplayListData::push(size_t pod, Args&&... args) { size_t skip = SkAlignPtr(sizeof(T) + pod); @@ -736,6 +757,14 @@ void* DisplayListData::push(size_t pod, Args&&... args) { new (op) T{std::forward<Args>(args)...}; op->type = (uint32_t)T::kType; op->skip = skip; + + // check if this is a fill op or not, in case we need to avoid messing with it with force invert + if constexpr (!std::is_same_v<T, DrawTextBlob>) { + if (hasPaintWithFill(args...)) { + mHasFill = true; + } + } + return op + 1; } diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 4f54ee286a56..afadbfda7471 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -110,7 +110,7 @@ class RecordingCanvas; class DisplayListData final { public: - DisplayListData() : mHasText(false) {} + DisplayListData() : mHasText(false), mHasFill(false) {} ~DisplayListData(); void draw(SkCanvas* canvas) const; @@ -121,6 +121,7 @@ public: void applyColorTransform(ColorTransform transform); bool hasText() const { return mHasText; } + bool hasFill() const { return mHasFill; } size_t usedSize() const { return fUsed; } size_t allocatedSize() const { return fReserved; } @@ -192,6 +193,7 @@ private: size_t fReserved = 0; bool mHasText : 1; + bool mHasFill : 1; }; class RecordingCanvas final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> { diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 3e131bc44d39..0b42c88aa448 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -427,7 +427,13 @@ void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { children.push_back(node); }); if (mDisplayList.hasText()) { - usage = UsageHint::Foreground; + if (mDisplayList.hasFill()) { + // Handle a special case for custom views that draw both text and background in the + // same RenderNode, which would otherwise be altered to white-on-white text. + usage = UsageHint::Container; + } else { + usage = UsageHint::Foreground; + } } if (usage == UsageHint::Unknown) { if (children.size() > 1) { @@ -453,8 +459,13 @@ void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { drawn.join(bounds); } } - mDisplayList.applyColorTransform( - usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light); + + if (usage == UsageHint::Container) { + mDisplayList.applyColorTransform(ColorTransform::Invert); + } else { + mDisplayList.applyColorTransform(usage == UsageHint::Background ? ColorTransform::Dark + : ColorTransform::Light); + } } void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) { diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp index 4dbfa88d6301..c55066af3612 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.cpp +++ b/libs/hwui/jni/YuvToJpegEncoder.cpp @@ -332,7 +332,8 @@ ultrahdr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNI bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace, - int width, int height, int jpegQuality) { + int width, int height, int jpegQuality, ScopedByteArrayRO* jExif, + ScopedIntArrayRO* jHdrStrides, ScopedIntArrayRO* jSdrStrides) { // Check SDR color space. Now we only support SRGB transfer function if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) != ADataSpace::TRANSFER_SRGB) { jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); @@ -340,6 +341,19 @@ bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, "The requested SDR color space is not supported. Transfer function must be SRGB"); return false; } + // Check HDR and SDR strides length. + // HDR is YCBCR_P010 color format, and its strides length must be 2 (Y, chroma (Cb, Cr)). + // SDR is YUV_420_888 color format, and its strides length must be 3 (Y, Cb, Cr). + if (jHdrStrides->size() != 2) { + jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); + env->ThrowNew(IllegalArgumentException, "HDR stride length must be 2."); + return false; + } + if (jSdrStrides->size() != 3) { + jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); + env->ThrowNew(IllegalArgumentException, "SDR stride length must be 3."); + return false; + } ultrahdr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace); ultrahdr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace); @@ -351,20 +365,32 @@ bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, return false; } + const int* hdrStrides = reinterpret_cast<const int*>(jHdrStrides->get()); + const int* sdrStrides = reinterpret_cast<const int*>(jSdrStrides->get()); + JpegR jpegREncoder; jpegr_uncompressed_struct p010; p010.data = hdr; p010.width = width; p010.height = height; + // Divided by 2 because unit in libultrader is pixel and in YuvImage it is byte. + p010.luma_stride = (hdrStrides[0] + 1) / 2; + p010.chroma_stride = (hdrStrides[1] + 1) / 2; p010.colorGamut = hdrColorGamut; jpegr_uncompressed_struct yuv420; yuv420.data = sdr; yuv420.width = width; yuv420.height = height; + yuv420.luma_stride = sdrStrides[0]; + yuv420.chroma_stride = sdrStrides[1]; yuv420.colorGamut = sdrColorGamut; + jpegr_exif_struct exif; + exif.data = const_cast<void*>(reinterpret_cast<const void*>(jExif->get())); + exif.length = jExif->size(); + jpegr_compressed_struct jpegR; jpegR.maxLength = width * height * sizeof(uint8_t); @@ -373,7 +399,8 @@ bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420, hdrTransferFunction, - &jpegR, jpegQuality, nullptr); success != android::OK) { + &jpegR, jpegQuality, + exif.length > 0 ? &exif : NULL); success != android::OK) { ALOGW("Encode JPEG/R failed, error code: %d.", success); return false; } @@ -415,20 +442,27 @@ static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv, static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr, jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace, jint width, jint height, jint quality, jobject jstream, - jbyteArray jstorage) { + jbyteArray jstorage, jbyteArray jExif, + jintArray jHdrStrides, jintArray jSdrStrides) { jbyte* hdr = env->GetByteArrayElements(inHdr, NULL); jbyte* sdr = env->GetByteArrayElements(inSdr, NULL); + ScopedByteArrayRO exif(env, jExif); + ScopedIntArrayRO hdrStrides(env, jHdrStrides); + ScopedIntArrayRO sdrStrides(env, jSdrStrides); + SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); P010Yuv420ToJpegREncoder encoder; jboolean result = JNI_FALSE; if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace, - width, height, quality)) { + width, height, quality, &exif, + &hdrStrides, &sdrStrides)) { result = JNI_TRUE; } env->ReleaseByteArrayElements(inHdr, hdr, 0); env->ReleaseByteArrayElements(inSdr, sdr, 0); + delete strm; return result; } @@ -437,7 +471,7 @@ static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr, static const JNINativeMethod gYuvImageMethods[] = { { "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z", (void*)YuvImage_compressToJpeg }, - { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B)Z", + { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B[B[I[I)Z", (void*)YuvImage_compressToJpegR } }; diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h index 8ef780547184..a3a322453be7 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.h +++ b/libs/hwui/jni/YuvToJpegEncoder.h @@ -2,6 +2,7 @@ #define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ #include <android/data_space.h> +#include <nativehelper/ScopedPrimitiveArray.h> #include <ultrahdr/jpegr.h> extern "C" { @@ -90,11 +91,15 @@ public: * @param width Width of the Yuv data in terms of pixels. * @param height Height of the Yuv data in terms of pixels. * @param jpegQuality Picture quality in [0, 100]. + * @param exif Buffer holds EXIF package. + * @param hdrStrides The number of row bytes in each image plane of the HDR input. + * @param sdrStrides The number of row bytes in each image plane of the SDR input. * @return true if successfully compressed the stream. */ bool encode(JNIEnv* env, SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace, - int width, int height, int jpegQuality); + int width, int height, int jpegQuality, ScopedByteArrayRO* exif, + ScopedIntArrayRO* hdrStrides, ScopedIntArrayRO* sdrStrides); /** Map data space (defined in DataSpace.java and data_space.h) to the color gamut * used in JPEG/R diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 2b2e3995d17e..ffa915ad968c 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -56,6 +56,7 @@ void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas, int nestLevel) const { LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver); for (auto& child : displayList.mChildNodes) { + if (!child.getRenderNode()->isRenderable()) continue; const RenderProperties& childProperties = child.getNodeProperties(); // immediate children cannot be projected on their parent diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index e5bd5c9b2a3b..b9dc1c49f09e 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -96,6 +96,8 @@ public: bool hasText() const { return mDisplayList.hasText(); } + bool hasFill() const { return mDisplayList.hasFill(); } + /** * Attempts to reset and reuse this DisplayList. * diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp index 6df34bee2224..64d38b9ef466 100644 --- a/libs/hwui/renderthread/ReliableSurface.cpp +++ b/libs/hwui/renderthread/ReliableSurface.cpp @@ -150,11 +150,11 @@ ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer(int error) { } AHardwareBuffer_Desc desc = AHardwareBuffer_Desc{ - .usage = mUsage, - .format = mFormat, .width = 1, .height = 1, .layers = 1, + .format = mFormat, + .usage = mUsage, .rfu0 = 0, .rfu1 = 0, }; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index c3c136feef69..eab36050896f 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -123,7 +123,7 @@ void RenderProxy::setSurfaceControl(ASurfaceControl* surfaceControl) { } void RenderProxy::allocateBuffers() { - mRenderThread.queue().post([=]() { mContext->allocateBuffers(); }); + mRenderThread.queue().post([this]() { mContext->allocateBuffers(); }); } bool RenderProxy::pause() { @@ -136,15 +136,16 @@ void RenderProxy::setStopped(bool stopped) { void RenderProxy::setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { mRenderThread.queue().post( - [=]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); }); + [=, this]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); }); } void RenderProxy::setLightGeometry(const Vector3& lightCenter, float lightRadius) { - mRenderThread.queue().post([=]() { mContext->setLightGeometry(lightCenter, lightRadius); }); + mRenderThread.queue().post( + [=, this]() { mContext->setLightGeometry(lightCenter, lightRadius); }); } void RenderProxy::setOpaque(bool opaque) { - mRenderThread.queue().post([=]() { mContext->setOpaque(opaque); }); + mRenderThread.queue().post([=, this]() { mContext->setOpaque(opaque); }); } float RenderProxy::setColorMode(ColorMode mode) { @@ -152,9 +153,9 @@ float RenderProxy::setColorMode(ColorMode mode) { // an async call since we already know the return value if (mode == ColorMode::Hdr || mode == ColorMode::Hdr10) { return mRenderThread.queue().runSync( - [=]() -> float { return mContext->setColorMode(mode); }); + [=, this]() -> float { return mContext->setColorMode(mode); }); } else { - mRenderThread.queue().post([=]() { mContext->setColorMode(mode); }); + mRenderThread.queue().post([=, this]() { mContext->setColorMode(mode); }); return 1.f; } } @@ -179,7 +180,7 @@ void RenderProxy::destroy() { // destroyCanvasAndSurface() needs a fence as when it returns the // underlying BufferQueue is going to be released from under // the render thread. - mRenderThread.queue().runSync([=]() { mContext->destroy(); }); + mRenderThread.queue().runSync([this]() { mContext->destroy(); }); } void RenderProxy::destroyFunctor(int functor) { @@ -300,7 +301,7 @@ void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) { } void RenderProxy::resetProfileInfo() { - mRenderThread.queue().runSync([=]() { + mRenderThread.queue().runSync([this]() { std::lock_guard lock(mRenderThread.getJankDataMutex()); mContext->resetFrameStats(); }); @@ -355,15 +356,15 @@ int RenderProxy::getRenderThreadTid() { } void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) { - mRenderThread.queue().post([=]() { mContext->addRenderNode(node, placeFront); }); + mRenderThread.queue().post([=, this]() { mContext->addRenderNode(node, placeFront); }); } void RenderProxy::removeRenderNode(RenderNode* node) { - mRenderThread.queue().post([=]() { mContext->removeRenderNode(node); }); + mRenderThread.queue().post([=, this]() { mContext->removeRenderNode(node); }); } void RenderProxy::drawRenderNode(RenderNode* node) { - mRenderThread.queue().runSync([=]() { mContext->prepareAndDraw(node); }); + mRenderThread.queue().runSync([=, this]() { mContext->prepareAndDraw(node); }); } void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) { diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index f76ea063842f..623ee4e6c27e 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -150,7 +150,7 @@ void RenderThread::frameCallback(int64_t vsyncId, int64_t frameDeadline, int64_t ATRACE_FORMAT("queue mFrameCallbackTask to run after %.2fms", toFloatMillis(runAt - SteadyClock::now()).count()); queue().postAt(toNsecs_t(runAt.time_since_epoch()).count(), - [=]() { dispatchFrameCallbacks(); }); + [this]() { dispatchFrameCallbacks(); }); } } diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp index 1f6edf36af25..18c50472a7df 100644 --- a/libs/hwui/tests/unit/CanvasOpTests.cpp +++ b/libs/hwui/tests/unit/CanvasOpTests.cpp @@ -225,9 +225,9 @@ TEST(CanvasOp, simpleDrawLines) { TEST(CanvasOp, simpleDrawRect) { CanvasOpBuffer buffer; EXPECT_EQ(buffer.size(), 0); - buffer.push<Op::DrawRect> ({ - .paint = SkPaint{}, - .rect = SkRect::MakeEmpty() + buffer.push<Op::DrawRect>({ + .rect = SkRect::MakeEmpty(), + .paint = SkPaint{}, }); CallCountingCanvas canvas; @@ -242,9 +242,9 @@ TEST(CanvasOp, simpleDrawRegionRect) { EXPECT_EQ(buffer.size(), 0); SkRegion region; region.setRect(SkIRect::MakeWH(12, 50)); - buffer.push<Op::DrawRegion> ({ - .paint = SkPaint{}, - .region = region + buffer.push<Op::DrawRegion>({ + .region = region, + .paint = SkPaint{}, }); CallCountingCanvas canvas; @@ -264,9 +264,9 @@ TEST(CanvasOp, simpleDrawRegionPath) { clip.setRect(SkIRect::MakeWH(100, 100)); SkRegion region; region.setPath(path, clip); - buffer.push<Op::DrawRegion> ({ - .paint = SkPaint{}, - .region = region + buffer.push<Op::DrawRegion>({ + .region = region, + .paint = SkPaint{}, }); CallCountingCanvas canvas; @@ -279,11 +279,11 @@ TEST(CanvasOp, simpleDrawRegionPath) { TEST(CanvasOp, simpleDrawRoundRect) { CanvasOpBuffer buffer; EXPECT_EQ(buffer.size(), 0); - buffer.push<Op::DrawRoundRect> ({ - .paint = SkPaint{}, - .rect = SkRect::MakeEmpty(), - .rx = 10, - .ry = 10 + buffer.push<Op::DrawRoundRect>({ + .rect = SkRect::MakeEmpty(), + .rx = 10, + .ry = 10, + .paint = SkPaint{}, }); CallCountingCanvas canvas; @@ -611,9 +611,9 @@ TEST(CanvasOp, immediateRendering) { EXPECT_EQ(0, canvas->sumTotalDrawCalls()); ImmediateModeRasterizer rasterizer{canvas}; - auto op = CanvasOp<Op::DrawRect> { - .paint = SkPaint{}, - .rect = SkRect::MakeEmpty() + auto op = CanvasOp<Op::DrawRect>{ + .rect = SkRect::MakeEmpty(), + .paint = SkPaint{}, }; EXPECT_TRUE(CanvasOpTraits::can_draw<decltype(op)>); rasterizer.draw(op); diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index 8273524167f9..e727ea899098 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -331,3 +331,31 @@ RENDERTHREAD_TEST(DISABLED_RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage); canvasContext->destroy(); } + +TEST(RenderNode, hasNoFill) { + auto rootNode = + TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) { + Paint paint; + paint.setStyle(SkPaint::Style::kStroke_Style); + canvas.drawRect(10, 10, 100, 100, paint); + }); + + TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode); + + EXPECT_FALSE(rootNode.get()->getDisplayList().hasFill()); + EXPECT_FALSE(rootNode.get()->getDisplayList().hasText()); +} + +TEST(RenderNode, hasFill) { + auto rootNode = + TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) { + Paint paint; + paint.setStyle(SkPaint::kStrokeAndFill_Style); + canvas.drawRect(10, 10, 100, 100, paint); + }); + + TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode); + + EXPECT_TRUE(rootNode.get()->getDisplayList().hasFill()); + EXPECT_FALSE(rootNode.get()->getDisplayList().hasText()); +} diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING index a9da832b2a5a..8f5f1f6a4794 100644 --- a/media/TEST_MAPPING +++ b/media/TEST_MAPPING @@ -49,6 +49,18 @@ {"exclude-annotation": "org.junit.Ignore"} ] } + ], + "postsubmit": [ + { + "file_patterns": [ + "[^/]*(LoudnessCodec)[^/]*\\.java" + ], + "name": "LoudnessCodecApiTest", + "options": [ + { + "include-annotation": "android.platform.test.annotations.Presubmit" + } + ] + } ] } - diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 9f63dfdc0ccb..1e3234902a45 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -20,7 +20,6 @@ import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAUL import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.audio.Flags.autoPublicVolumeApiHardening; -import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API; import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API; import android.Manifest; @@ -51,6 +50,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.media.AudioAttributes.AttributeSystemUsage; import android.media.CallbackUtil.ListenerInfo; import android.media.audiopolicy.AudioPolicy; @@ -903,6 +903,7 @@ public class AudioManager { @UnsupportedAppUsage public AudioManager(Context context) { setContext(context); + initPlatform(); } private Context getContext() { @@ -916,6 +917,9 @@ public class AudioManager { } private void setContext(Context context) { + if (context == null) { + return; + } mOriginalContextDeviceId = context.getDeviceId(); mApplicationContext = context.getApplicationContext(); if (mApplicationContext != null) { @@ -1065,8 +1069,17 @@ public class AudioManager { * @see #isVolumeFixed() */ public void adjustVolume(int direction, @PublicVolumeFlags int flags) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); - helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags); + if (applyAutoHardening()) { + 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); + } } /** @@ -1095,8 +1108,17 @@ public class AudioManager { */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, @PublicVolumeFlags int flags) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); - helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags); + if (applyAutoHardening()) { + 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); + } } /** @hide */ @@ -2924,33 +2946,6 @@ public class AudioManager { } //==================================================================== - // Loudness management - private final Object mLoudnessCodecLock = new Object(); - - @GuardedBy("mLoudnessCodecLock") - private LoudnessCodecDispatcher mLoudnessCodecDispatcher = null; - - /** - * Creates a new instance of {@link LoudnessCodecConfigurator}. - * @return the {@link LoudnessCodecConfigurator} instance - * - * TODO: remove hide once API is final - * @hide - */ - @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public @NonNull LoudnessCodecConfigurator createLoudnessCodecConfigurator() { - LoudnessCodecConfigurator configurator; - synchronized (mLoudnessCodecLock) { - // initialize lazily - if (mLoudnessCodecDispatcher == null) { - mLoudnessCodecDispatcher = new LoudnessCodecDispatcher(this); - } - configurator = mLoudnessCodecDispatcher.createLoudnessCodecConfigurator(); - } - return configurator; - } - - //==================================================================== // Bluetooth SCO control /** * Sticky broadcast intent action indicating that the Bluetooth SCO audio @@ -9998,6 +9993,30 @@ public class AudioManager { } } + //==================================================================== + // Flag related utilities + + private boolean mIsAutomotive = false; + + private void initPlatform() { + try { + final Context context = getContext(); + if (context != null) { + mIsAutomotive = context.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } + } catch (Exception e) { + Log.e(TAG, "Error querying system feature for AUTOMOTIVE", e); + } + } + + private boolean applyAutoHardening() { + if (mIsAutomotive && autoPublicVolumeApiHardening()) { + return true; + } + return false; + } + //--------------------------------------------------------- // Inner classes //-------------------- diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 61b5fd5fb0ec..367b38a152fc 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -2462,6 +2462,8 @@ public class AudioSystem public static final int PLATFORM_VOICE = 1; /** @hide The platform is a television or a set-top box */ public static final int PLATFORM_TELEVISION = 2; + /** @hide The platform is automotive */ + public static final int PLATFORM_AUTOMOTIVE = 3; /** * @hide @@ -2478,6 +2480,9 @@ public class AudioSystem return PLATFORM_VOICE; } else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { return PLATFORM_TELEVISION; + } else if (context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE)) { + return PLATFORM_AUTOMOTIVE; } else { return PLATFORM_DEFAULT; } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index b4ca485eb764..d14775fa9ad9 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -52,7 +52,7 @@ import android.media.ISpatializerHeadToSoundStagePoseCallback; import android.media.ISpatializerOutputCallback; import android.media.IStreamAliasingDispatcher; import android.media.IVolumeController; -import android.media.LoudnessCodecFormat; +import android.media.LoudnessCodecInfo; import android.media.PlayerBase; import android.media.VolumeInfo; import android.media.VolumePolicy; @@ -500,6 +500,10 @@ 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); @@ -731,15 +735,13 @@ interface IAudioService { void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher); - oneway void startLoudnessCodecUpdates(in int piid); - - oneway void stopLoudnessCodecUpdates(in int piid); + oneway void startLoudnessCodecUpdates(int piid, in List<LoudnessCodecInfo> codecInfoSet); - oneway void addLoudnesssCodecFormat(in int piid, in LoudnessCodecFormat format); + oneway void stopLoudnessCodecUpdates(int piid); - oneway void addLoudnesssCodecFormatList(in int piid, in List<LoudnessCodecFormat> format); + oneway void addLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo); - oneway void removeLoudnessCodecFormat(in int piid, in LoudnessCodecFormat format); + oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo); - PersistableBundle getLoudnessParams(in int piid, in LoudnessCodecFormat format); + PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo); } diff --git a/media/java/android/media/LoudnessCodecConfigurator.java b/media/java/android/media/LoudnessCodecConfigurator.java index 409abc211cb6..92f337244daf 100644 --- a/media/java/android/media/LoudnessCodecConfigurator.java +++ b/media/java/android/media/LoudnessCodecConfigurator.java @@ -16,6 +16,9 @@ package android.media; +import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID; +import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4; +import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D; import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API; import android.annotation.CallbackExecutor; @@ -23,21 +26,27 @@ import android.annotation.FlaggedApi; import android.os.Bundle; import android.util.Log; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; /** * Class for getting recommended loudness parameter updates for audio decoders, according to the * encoded format and current audio routing. Those updates can be automatically applied to the * {@link MediaCodec} instance(s), or be provided to the user. The codec loudness management - * updates are defined by the CTA-2075 standard. + * parameter updates are defined by the CTA-2075 standard. * <p>A new object should be instantiated for each {@link AudioTrack} with the help - * of {@link AudioManager#createLoudnessCodecConfigurator()}. + * of {@link #create()} or {@link #create(Executor, OnLoudnessCodecUpdateListener)}. * * TODO: remove hide once API is final * @hide @@ -81,120 +90,255 @@ public class LoudnessCodecConfigurator { @NonNull private final LoudnessCodecDispatcher mLcDispatcher; + private final Object mConfiguratorLock = new Object(); + + @GuardedBy("mConfiguratorLock") private AudioTrack mAudioTrack; - private final List<MediaCodec> mMediaCodecs = new ArrayList<>(); + @GuardedBy("mConfiguratorLock") + private final Executor mExecutor; - /** @hide */ - protected LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher) { - mLcDispatcher = Objects.requireNonNull(lcDispatcher); - } + @GuardedBy("mConfiguratorLock") + private final OnLoudnessCodecUpdateListener mListener; + @GuardedBy("mConfiguratorLock") + private final HashMap<LoudnessCodecInfo, Set<MediaCodec>> mMediaCodecs = new HashMap<>(); /** - * Starts receiving asynchronous loudness updates and registers the listener for - * receiving {@link MediaCodec} loudness parameter updates. - * <p>This method should be called before {@link #startLoudnessCodecUpdates()} or - * after {@link #stopLoudnessCodecUpdates()}. + * Creates a new instance of {@link LoudnessCodecConfigurator} * - * @param executor {@link Executor} to handle the callbacks - * @param listener used to receive updates + * <p>This method should be used when the client does not need to alter the + * codec loudness parameters before they are applied to the audio decoders. + * Otherwise, use {@link #create(Executor, OnLoudnessCodecUpdateListener)}. * - * @return {@code true} if there is at least one {@link MediaCodec} and - * {@link AudioTrack} set and the user can expect receiving updates. + * @return the {@link LoudnessCodecConfigurator} instance * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public boolean startLoudnessCodecUpdates(@NonNull @CallbackExecutor Executor executor, - @NonNull OnLoudnessCodecUpdateListener listener) { - Objects.requireNonNull(executor, - "Executor must not be null"); - Objects.requireNonNull(listener, - "OnLoudnessCodecUpdateListener must not be null"); - mLcDispatcher.addLoudnessCodecListener(this, executor, listener); - - return checkStartLoudnessConfigurator(); + public static @NonNull LoudnessCodecConfigurator create() { + return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()), + Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {}); } /** - * Starts receiving asynchronous loudness updates. - * <p>The registered MediaCodecs will be updated automatically without any client - * callbacks. + * Creates a new instance of {@link LoudnessCodecConfigurator} * - * @return {@code true} if there is at least one MediaCodec and AudioTrack set - * (see {@link #setAudioTrack(AudioTrack)}, {@link #addMediaCodec(MediaCodec)}) - * and the user can expect receiving updates. + * <p>This method should be used when the client wants to alter the codec + * loudness parameters before they are applied to the audio decoders. + * Otherwise, use {@link #create()}. + * + * @param executor {@link Executor} to handle the callbacks + * @param listener used for receiving updates + * + * @return the {@link LoudnessCodecConfigurator} instance * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public boolean startLoudnessCodecUpdates() { - mLcDispatcher.addLoudnessCodecListener(this, - Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {}); - return checkStartLoudnessConfigurator(); + public static @NonNull LoudnessCodecConfigurator create( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnLoudnessCodecUpdateListener listener) { + Objects.requireNonNull(executor, "Executor cannot be null"); + Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null"); + + return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()), + executor, listener); } /** - * Stops receiving asynchronous loudness updates. + * Creates a new instance of {@link LoudnessCodecConfigurator} + * + * <p>This method should be used only in testing + * + * @param service interface for communicating with AudioService + * @param executor {@link Executor} to handle the callbacks + * @param listener used for receiving updates + * + * @return the {@link LoudnessCodecConfigurator} instance * - * TODO: remove hide once API is final * @hide */ - @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public void stopLoudnessCodecUpdates() { - mLcDispatcher.removeLoudnessCodecListener(this); + public static @NonNull LoudnessCodecConfigurator createForTesting( + @NonNull IAudioService service, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnLoudnessCodecUpdateListener listener) { + Objects.requireNonNull(service, "IAudioService cannot be null"); + Objects.requireNonNull(executor, "Executor cannot be null"); + Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null"); + + return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(service), + executor, listener); + } + + /** @hide */ + private LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnLoudnessCodecUpdateListener listener) { + mLcDispatcher = Objects.requireNonNull(lcDispatcher, "Dispatcher cannot be null"); + mExecutor = Objects.requireNonNull(executor, "Executor cannot be null"); + mListener = Objects.requireNonNull(listener, + "OnLoudnessCodecUpdateListener cannot be null"); } /** - * Adds a new {@link MediaCodec} that will stream data to an {@link AudioTrack} - * which is registered through {@link #setAudioTrack(AudioTrack)}. + * Sets the {@link AudioTrack} and starts receiving asynchronous updates for + * the registered {@link MediaCodec}s (see {@link #addMediaCodec(MediaCodec)}) + * + * <p>The AudioTrack should be the one that receives audio data from the + * added audio decoders and is used to determine the device routing on which + * the audio streaming will take place. This will directly influence the + * loudness parameters. + * <p>After calling this method the framework will compute the initial set of + * parameters which will be applied to the registered codecs/returned to the + * listener for modification. + * + * @param audioTrack the track that will receive audio data from the provided + * audio decoders. In case this is {@code null} this + * method will have the effect of clearing the existing set + * {@link AudioTrack} and will stop receiving asynchronous + * loudness updates * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public void addMediaCodec(@NonNull MediaCodec mediaCodec) { - mMediaCodecs.add(Objects.requireNonNull(mediaCodec, - "MediaCodec for addMediaCodec must not be null")); + public void setAudioTrack(AudioTrack audioTrack) { + List<LoudnessCodecInfo> codecInfos; + int piid = PLAYER_PIID_INVALID; + int oldPiid = PLAYER_PIID_INVALID; + synchronized (mConfiguratorLock) { + if (mAudioTrack != null && mAudioTrack == audioTrack) { + Log.v(TAG, "Loudness configurator already started for piid: " + + mAudioTrack.getPlayerIId()); + return; + } + + codecInfos = getLoudnessCodecInfoList_l(); + if (mAudioTrack != null) { + oldPiid = mAudioTrack.getPlayerIId(); + mLcDispatcher.removeLoudnessCodecListener(this); + } + if (audioTrack != null) { + piid = audioTrack.getPlayerIId(); + mLcDispatcher.addLoudnessCodecListener(this, mExecutor, mListener); + } + + mAudioTrack = audioTrack; + } + + if (oldPiid != PLAYER_PIID_INVALID) { + Log.v(TAG, "Loudness configurator stopping updates for piid: " + oldPiid); + mLcDispatcher.stopLoudnessCodecUpdates(oldPiid); + } + if (piid != PLAYER_PIID_INVALID) { + Log.v(TAG, "Loudness configurator starting updates for piid: " + piid); + mLcDispatcher.startLoudnessCodecUpdates(piid, codecInfos); + } } /** - * Removes the {@link MediaCodec} from receiving loudness updates. + * Adds a new {@link MediaCodec} that will stream data to an {@link AudioTrack} + * which the client sets + * (see {@link LoudnessCodecConfigurator#setAudioTrack(AudioTrack)}). + * + * <p>This method can be called while asynchronous updates are live. + * + * <p>No new element will be added if the passed {@code mediaCodec} was + * previously added. + * + * @param mediaCodec the codec to start receiving asynchronous loudness + * updates * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public void removeMediaCodec(@NonNull MediaCodec mediaCodec) { - mMediaCodecs.remove(Objects.requireNonNull(mediaCodec, - "MediaCodec for removeMediaCodec must not be null")); + public void addMediaCodec(@NonNull MediaCodec mediaCodec) { + final MediaCodec mc = Objects.requireNonNull(mediaCodec, + "MediaCodec for addMediaCodec cannot be null"); + int piid = PLAYER_PIID_INVALID; + final LoudnessCodecInfo mcInfo = getCodecInfo(mc); + + if (mcInfo != null) { + synchronized (mConfiguratorLock) { + final AtomicBoolean containsCodec = new AtomicBoolean(false); + Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> { + containsCodec.set(!codecSet.add(mc)); + return codecSet; + }); + if (newSet == null) { + newSet = new HashSet<>(); + newSet.add(mc); + mMediaCodecs.put(mcInfo, newSet); + } + if (containsCodec.get()) { + Log.v(TAG, "Loudness configurator already added media codec " + mediaCodec); + return; + } + if (mAudioTrack != null) { + piid = mAudioTrack.getPlayerIId(); + } + } + + if (piid != PLAYER_PIID_INVALID) { + mLcDispatcher.addLoudnessCodecInfo(piid, mcInfo); + } + } } /** - * Sets the {@link AudioTrack} that can receive audio data from the added - * {@link MediaCodec}'s. The {@link AudioTrack} is used to determine the devices - * on which the streaming will take place and hence will directly influence the - * loudness params. - * <p>Should be called before starting the loudness updates - * (see {@link #startLoudnessCodecUpdates()}, - * {@link #startLoudnessCodecUpdates(Executor, OnLoudnessCodecUpdateListener)}) + * Removes the {@link MediaCodec} from receiving loudness updates. + * + * <p>This method can be called while asynchronous updates are live. + * + * <p>No elements will be removed if the passed mediaCodec was not added before. + * + * @param mediaCodec the element to remove for receiving asynchronous updates * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public void setAudioTrack(@NonNull AudioTrack audioTrack) { - mAudioTrack = Objects.requireNonNull(audioTrack, - "AudioTrack for setAudioTrack must not be null"); + public void removeMediaCodec(@NonNull MediaCodec mediaCodec) { + int piid = PLAYER_PIID_INVALID; + LoudnessCodecInfo mcInfo; + AtomicBoolean removed = new AtomicBoolean(false); + + mcInfo = getCodecInfo(Objects.requireNonNull(mediaCodec, + "MediaCodec for removeMediaCodec cannot be null")); + + if (mcInfo != null) { + synchronized (mConfiguratorLock) { + if (mAudioTrack != null) { + piid = mAudioTrack.getPlayerIId(); + } + mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> { + removed.set(mcs.remove(mediaCodec)); + if (mcs.isEmpty()) { + // remove the entry + return null; + } + return mcs; + }); + } + + if (piid != PLAYER_PIID_INVALID && removed.get()) { + mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo); + } + } } /** - * Gets synchronous loudness updates when no listener is required and at least one - * {@link MediaCodec} which streams to a registered {@link AudioTrack} is set. - * Otherwise, an empty {@link Bundle} will be returned. + * Gets synchronous loudness updates when no listener is required. The provided + * {@link MediaCodec} streams audio data to the passed {@link AudioTrack}. + * + * @param audioTrack track that receives audio data from the passed + * {@link MediaCodec} + * @param mediaCodec codec that decodes loudness annotated data for the passed + * {@link AudioTrack} * * @return the {@link Bundle} containing the current loudness parameters. Caller is * responsible to update the {@link MediaCodec} @@ -204,22 +348,89 @@ public class LoudnessCodecConfigurator { */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) @NonNull - public Bundle getLoudnessCodecParams(@NonNull MediaCodec mediaCodec) { - // TODO: implement synchronous loudness params updates - return new Bundle(); + public Bundle getLoudnessCodecParams(@NonNull AudioTrack audioTrack, + @NonNull MediaCodec mediaCodec) { + Objects.requireNonNull(audioTrack, "Passed audio track cannot be null"); + + LoudnessCodecInfo codecInfo = getCodecInfo(mediaCodec); + if (codecInfo == null) { + return new Bundle(); + } + + return mLcDispatcher.getLoudnessCodecParams(audioTrack.getPlayerIId(), codecInfo); + } + + /** @hide */ + /*package*/ int getAssignedTrackPiid() { + int piid = PLAYER_PIID_INVALID; + + synchronized (mConfiguratorLock) { + if (mAudioTrack == null) { + return piid; + } + piid = mAudioTrack.getPlayerIId(); + } + + return piid; } - private boolean checkStartLoudnessConfigurator() { - if (mAudioTrack == null) { - Log.w(TAG, "Cannot start loudness configurator without an AudioTrack"); - return false; + /** @hide */ + /*package*/ List<MediaCodec> getRegisteredMediaCodecList() { + synchronized (mConfiguratorLock) { + return mMediaCodecs.values().stream().flatMap(Collection::stream).toList(); + } + } + + @GuardedBy("mConfiguratorLock") + private List<LoudnessCodecInfo> getLoudnessCodecInfoList_l() { + return mMediaCodecs.values().stream().flatMap(listMc -> listMc.stream().map( + LoudnessCodecConfigurator::getCodecInfo)).toList(); + } + + @Nullable + private static LoudnessCodecInfo getCodecInfo(@NonNull MediaCodec mediaCodec) { + LoudnessCodecInfo lci = new LoudnessCodecInfo(); + final MediaCodecInfo codecInfo = mediaCodec.getCodecInfo(); + if (codecInfo.isEncoder()) { + // loudness info only for decoders + Log.w(TAG, "MediaCodec used for encoding does not support loudness annotation"); + return null; } - if (mMediaCodecs.isEmpty()) { - Log.w(TAG, "Cannot start loudness configurator without at least one MediaCodec"); - return false; + final MediaFormat inputFormat = mediaCodec.getInputFormat(); + final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME); + if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) { + // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize one of + // these two keys + int aacProfile = -1; + int profile = -1; + try { + aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE); + } catch (NullPointerException e) { + // does not contain KEY_AAC_PROFILE. do nothing + } + try { + profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE); + } catch (NullPointerException e) { + // does not contain KEY_PROFILE. do nothing + } + if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE + || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) { + lci.metadataType = CODEC_METADATA_TYPE_MPEG_D; + } else { + lci.metadataType = CODEC_METADATA_TYPE_MPEG_4; + } + } else { + Log.w(TAG, "MediaCodec mime type not supported for loudness annotation"); + return null; } - return true; + final MediaFormat outputFormat = mediaCodec.getOutputFormat(); + lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) + < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); + + lci.mediaCodecHashCode = mediaCodec.hashCode(); + + return lci; } } diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java index fc5c354b98f5..be881b11e545 100644 --- a/media/java/android/media/LoudnessCodecDispatcher.java +++ b/media/java/android/media/LoudnessCodecDispatcher.java @@ -16,94 +16,217 @@ package android.media; +import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE; +import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION; +import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL; + import android.annotation.CallbackExecutor; import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener; +import android.os.Bundle; import android.os.PersistableBundle; import android.os.RemoteException; +import android.util.Log; import androidx.annotation.NonNull; import java.util.HashMap; +import java.util.List; import java.util.Map.Entry; import java.util.Objects; import java.util.concurrent.Executor; /** * Class used to handle the loudness related communication with the audio service. + * * @hide */ -public class LoudnessCodecDispatcher { - private final class LoudnessCodecUpdatesDispatcherStub - extends ILoudnessCodecUpdatesDispatcher.Stub - implements CallbackUtil.DispatcherStub { +public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { + private static final String TAG = "LoudnessCodecDispatcher"; + + private static final boolean DEBUG = false; + + private static final class LoudnessCodecUpdatesDispatcherStub + extends ILoudnessCodecUpdatesDispatcher.Stub { + private static LoudnessCodecUpdatesDispatcherStub sLoudnessCodecStub; + + private final CallbackUtil.LazyListenerManager<OnLoudnessCodecUpdateListener> + mLoudnessListenerMgr = new CallbackUtil.LazyListenerManager<>(); + + private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> + mConfiguratorListener = new HashMap<>(); + + public static synchronized LoudnessCodecUpdatesDispatcherStub getInstance() { + if (sLoudnessCodecStub == null) { + sLoudnessCodecStub = new LoudnessCodecUpdatesDispatcherStub(); + } + return sLoudnessCodecStub; + } + + private LoudnessCodecUpdatesDispatcherStub() {} + @Override public void dispatchLoudnessCodecParameterChange(int piid, PersistableBundle params) { mLoudnessListenerMgr.callListeners(listener -> - mConfiguratorListener.computeIfPresent(listener, (l, c) -> { - // TODO: send the bundle for the user to update - return c; + mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> { + // send the appropriate bundle for the user to update + if (lcConfig.getAssignedTrackPiid() == piid) { + final List<MediaCodec> mediaCodecs = + lcConfig.getRegisteredMediaCodecList(); + for (MediaCodec mediaCodec : mediaCodecs) { + final String infoKey = Integer.toString(mediaCodec.hashCode()); + if (params.containsKey(infoKey)) { + Bundle bundle = new Bundle( + params.getPersistableBundle(infoKey)); + if (DEBUG) { + Log.d(TAG, + "Received for piid " + piid + " bundle: " + bundle); + } + bundle = + LoudnessCodecUpdatesDispatcherStub.filterLoudnessParams( + l.onLoudnessCodecUpdate(mediaCodec, bundle)); + if (DEBUG) { + Log.d(TAG, "User changed for piid " + piid + + " to filtered bundle: " + bundle); + } + + if (!bundle.isDefinitelyEmpty()) { + mediaCodec.setParameters(bundle); + } + } + } + } + + return lcConfig; })); } - @Override - public void register(boolean register) { - try { - if (register) { - mAm.getService().registerLoudnessCodecUpdatesDispatcher(this); - } else { - mAm.getService().unregisterLoudnessCodecUpdatesDispatcher(this); - } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + private static Bundle filterLoudnessParams(Bundle bundle) { + Bundle filteredBundle = new Bundle(); + + if (bundle.containsKey(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)) { + filteredBundle.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, + bundle.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); + } + if (bundle.containsKey(KEY_AAC_DRC_HEAVY_COMPRESSION)) { + filteredBundle.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, + bundle.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION)); } + if (bundle.containsKey(KEY_AAC_DRC_EFFECT_TYPE)) { + filteredBundle.putInt(KEY_AAC_DRC_EFFECT_TYPE, + bundle.getInt(KEY_AAC_DRC_EFFECT_TYPE)); + } + + return filteredBundle; } - } - private final CallbackUtil.LazyListenerManager<OnLoudnessCodecUpdateListener> - mLoudnessListenerMgr = new CallbackUtil.LazyListenerManager<>(); + void addLoudnessCodecListener(@NonNull CallbackUtil.DispatcherStub dispatcher, + @NonNull LoudnessCodecConfigurator configurator, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnLoudnessCodecUpdateListener listener) { + Objects.requireNonNull(configurator); + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + + mLoudnessListenerMgr.addListener( + executor, listener, "addLoudnessCodecListener", + () -> dispatcher); + mConfiguratorListener.put(listener, configurator); + } - @NonNull private final LoudnessCodecUpdatesDispatcherStub mLoudnessCodecStub; + void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) { + Objects.requireNonNull(configurator); - private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> - mConfiguratorListener = new HashMap<>(); + for (Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e : + mConfiguratorListener.entrySet()) { + if (e.getValue() == configurator) { + final OnLoudnessCodecUpdateListener listener = e.getKey(); + mConfiguratorListener.remove(listener); + mLoudnessListenerMgr.removeListener(listener, "removeLoudnessCodecListener"); + break; + } + } + } + } - @NonNull private final AudioManager mAm; + @NonNull private final IAudioService mAudioService; - protected LoudnessCodecDispatcher(@NonNull AudioManager am) { - mAm = Objects.requireNonNull(am); - mLoudnessCodecStub = new LoudnessCodecUpdatesDispatcherStub(); + /** @hide */ + public LoudnessCodecDispatcher(@NonNull IAudioService audioService) { + mAudioService = Objects.requireNonNull(audioService); } - /** @hide */ - public LoudnessCodecConfigurator createLoudnessCodecConfigurator() { - return new LoudnessCodecConfigurator(this); + @Override + public void register(boolean register) { + try { + if (register) { + mAudioService.registerLoudnessCodecUpdatesDispatcher( + LoudnessCodecUpdatesDispatcherStub.getInstance()); + } else { + mAudioService.unregisterLoudnessCodecUpdatesDispatcher( + LoudnessCodecUpdatesDispatcherStub.getInstance()); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** @hide */ public void addLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator, @NonNull @CallbackExecutor Executor executor, @NonNull OnLoudnessCodecUpdateListener listener) { - Objects.requireNonNull(configurator); - Objects.requireNonNull(executor); - Objects.requireNonNull(listener); - - mConfiguratorListener.put(listener, configurator); - mLoudnessListenerMgr.addListener( - executor, listener, "addLoudnessCodecListener", () -> mLoudnessCodecStub); + LoudnessCodecUpdatesDispatcherStub.getInstance().addLoudnessCodecListener(this, + configurator, executor, listener); } /** @hide */ public void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) { - Objects.requireNonNull(configurator); - - for (Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e : - mConfiguratorListener.entrySet()) { - if (e.getValue() == configurator) { - final OnLoudnessCodecUpdateListener listener = e.getKey(); - mConfiguratorListener.remove(listener); - mLoudnessListenerMgr.removeListener(listener, "removeLoudnessCodecListener"); - break; - } + LoudnessCodecUpdatesDispatcherStub.getInstance().removeLoudnessCodecListener(configurator); + } + + /** @hide */ + public void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) { + try { + mAudioService.startLoudnessCodecUpdates(piid, codecInfoList); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public void stopLoudnessCodecUpdates(int piid) { + try { + mAudioService.stopLoudnessCodecUpdates(piid); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public void addLoudnessCodecInfo(int piid, @NonNull LoudnessCodecInfo mcInfo) { + try { + mAudioService.addLoudnessCodecInfo(piid, mcInfo); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public void removeLoudnessCodecInfo(int piid, @NonNull LoudnessCodecInfo mcInfo) { + try { + mAudioService.removeLoudnessCodecInfo(piid, mcInfo); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public Bundle getLoudnessCodecParams(int piid, @NonNull LoudnessCodecInfo mcInfo) { + Bundle loudnessParams = null; + try { + loudnessParams = new Bundle(mAudioService.getLoudnessParams(piid, mcInfo)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } + return loudnessParams; } } diff --git a/media/java/android/media/LoudnessCodecInfo.aidl b/media/java/android/media/LoudnessCodecInfo.aidl new file mode 100644 index 000000000000..fd695179057d --- /dev/null +++ b/media/java/android/media/LoudnessCodecInfo.aidl @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * Loudness information for a {@link MediaCodec} object which specifies the + * input attributes used for measuring the parameters required to perform + * loudness alignment as specified by the CTA2075 standard. + * + * {@hide} + */ +@JavaDerive(equals = true) +parcelable LoudnessCodecInfo { + /** Supported codec metadata types for loudness updates. */ + @Backing(type="int") + enum CodecMetadataType { + CODEC_METADATA_TYPE_INVALID = 0, + CODEC_METADATA_TYPE_MPEG_4 = 1, + CODEC_METADATA_TYPE_MPEG_D = 2, + CODEC_METADATA_TYPE_AC_3 = 3, + CODEC_METADATA_TYPE_AC_4 = 4, + CODEC_METADATA_TYPE_DTS_HD = 5, + CODEC_METADATA_TYPE_DTS_UHD = 6 + } + + int mediaCodecHashCode; + CodecMetadataType metadataType; + boolean isDownmixing; +}
\ No newline at end of file diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 6031ef70535d..94fce797f5d6 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -122,11 +122,6 @@ cc_library_shared { "-Wunused", "-Wunreachable-code", ], - - // Workaround Clang LTO crash. - lto: { - never: true, - }, } cc_library_shared { diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp index 8b5b726fd2db..cf5059ceb3c9 100644 --- a/media/jni/audioeffect/Android.bp +++ b/media/jni/audioeffect/Android.bp @@ -44,9 +44,4 @@ cc_library_shared { "-Wunreachable-code", "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION", ], - - // Workaround Clang LTO crash. - lto: { - never: true, - }, } diff --git a/media/tests/LoudnessCodecApiTest/Android.bp b/media/tests/LoudnessCodecApiTest/Android.bp new file mode 100644 index 000000000000..5ca0fc9661c2 --- /dev/null +++ b/media/tests/LoudnessCodecApiTest/Android.bp @@ -0,0 +1,27 @@ +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 { + name: "LoudnessCodecApiTest", + srcs: ["**/*.java"], + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.rules", + "junit", + "junit-params", + "mockito-target-minus-junit4", + "flag-junit", + "hamcrest-library", + "platform-test-annotations", + ], + platform_apis: true, + certificate: "platform", + resource_dirs: ["res"], + test_suites: ["device-tests"], +} diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_view.xml b/media/tests/LoudnessCodecApiTest/AndroidManifest.xml index 12b4e150ee2d..91a671fd6eef 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_view.xml +++ b/media/tests/LoudnessCodecApiTest/AndroidManifest.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2022 The Android Open Source Project +<!-- Copyright (C) 2020 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,13 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. --> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:padding="@dimen/ksh_item_padding" - android:layout_marginStart="@dimen/ksh_item_margin_start" - style="@style/ShortcutItemBackground" - android:textColor="?android:attr/textColorPrimary" - android:singleLine="false" - android:gravity="center" - android:textSize="@dimen/ksh_item_text_size"/>
\ No newline at end of file + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.loudnesscodecapitest"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.loudnesscodecapitest" + android:label="AudioManager loudness codec integration tests InstrumentationRunner"> + </instrumentation> +</manifest> diff --git a/media/tests/LoudnessCodecApiTest/AndroidTest.xml b/media/tests/LoudnessCodecApiTest/AndroidTest.xml new file mode 100644 index 000000000000..0099d986ac75 --- /dev/null +++ b/media/tests/LoudnessCodecApiTest/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Media Framework Tests"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="LoudnessCodecApiTest.apk" /> + </target_preparer> + + <option name="test-tag" value="LoudnessCodecApiTest" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.loudnesscodecapitest" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_icon_view.xml b/media/tests/LoudnessCodecApiTest/res/layout/loudnesscodecapitest.xml index a037cb284b5a..17fdba6f7c15 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_new_icon_view.xml +++ b/media/tests/LoudnessCodecApiTest/res/layout/loudnesscodecapitest.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2022 The Android Open Source Project +<!-- Copyright (C) 2020 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,12 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> -<ImageView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:padding="@dimen/ksh_item_padding" - android:layout_marginLeft="0dp" - android:layout_marginRight="0dp" - android:scaleType="fitXY" - android:tint="?android:attr/textColorPrimary" - style="@style/ShortcutItemBackground" />
\ No newline at end of file + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> +</LinearLayout> diff --git a/media/tests/LoudnessCodecApiTest/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a b/media/tests/LoudnessCodecApiTest/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a Binary files differnew file mode 100644 index 000000000000..acba4b354066 --- /dev/null +++ b/media/tests/LoudnessCodecApiTest/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a diff --git a/media/tests/LoudnessCodecApiTest/res/values/strings.xml b/media/tests/LoudnessCodecApiTest/res/values/strings.xml new file mode 100644 index 000000000000..0c4227c364ca --- /dev/null +++ b/media/tests/LoudnessCodecApiTest/res/values/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- name of the app [CHAR LIMIT=25]--> + <string name="app_name">Loudness Codec API Tests</string> +</resources> diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java new file mode 100644 index 000000000000..65a9799431e7 --- /dev/null +++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.loudnesscodecapitest; + +import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.AssetFileDescriptor; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioTrack; +import android.media.IAudioService; +import android.media.LoudnessCodecConfigurator; +import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener; +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.os.PersistableBundle; +import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.Log; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; +import java.util.concurrent.Executors; + +/** + * Unit tests for {@link LoudnessCodecConfigurator} checking the internal interactions with a mocked + * {@link IAudioService} without any real IPC interactions. + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +public class LoudnessCodecConfiguratorTest { + private static final String TAG = "LoudnessCodecConfiguratorTest"; + + private static final String TEST_MEDIA_AUDIO_CODEC_PREFIX = "audio/"; + private static final int TEST_AUDIO_TRACK_BUFFER_SIZE = 2048; + private static final int TEST_AUDIO_TRACK_SAMPLERATE = 48000; + private static final int TEST_AUDIO_TRACK_CHANNELS = 2; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Mock + private IAudioService mAudioService; + + private LoudnessCodecConfigurator mLcc; + + @Before + public void setUp() { + mLcc = LoudnessCodecConfigurator.createForTesting(mAudioService, + Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {}); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void setAudioTrack_callsAudioServiceStart() throws Exception { + final AudioTrack track = createAudioTrack(); + + mLcc.addMediaCodec(createAndConfigureMediaCodec()); + mLcc.setAudioTrack(track); + + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), + anyList()); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception { + when(mAudioService.getLoudnessParams(anyInt(), any())).thenReturn(new PersistableBundle()); + final AudioTrack track = createAudioTrack(); + + mLcc.getLoudnessCodecParams(track, createAndConfigureMediaCodec()); + + verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any()); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void setAudioTrack_addsAudioServicePiidCodecs() throws Exception { + final AudioTrack track = createAudioTrack(); + final MediaCodec mediaCodec = createAndConfigureMediaCodec(); + + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); + + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void setAudioTrackTwice_ignoresSecondCall() throws Exception { + final AudioTrack track = createAudioTrack(); + final MediaCodec mediaCodec = createAndConfigureMediaCodec(); + + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); + mLcc.setAudioTrack(track); + + verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()), + anyList()); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void setTrackNull_stopCodecUpdates() throws Exception { + final AudioTrack track = createAudioTrack(); + + mLcc.addMediaCodec(createAndConfigureMediaCodec()); + mLcc.setAudioTrack(track); + + mLcc.setAudioTrack(null); // stops updates + verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId())); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void addMediaCodecTwice_ignoresSecondCall() throws Exception { + final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class); + final AudioTrack track = createAudioTrack(); + final MediaCodec mediaCodec = createAndConfigureMediaCodec(); + + mLcc.addMediaCodec(mediaCodec); + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); + + verify(mAudioService, times(1)).startLoudnessCodecUpdates( + eq(track.getPlayerIId()), argument.capture()); + assertEquals(argument.getValue().size(), 1); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void setClearTrack_removeAllAudioServicePiidCodecs() throws Exception { + final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class); + + final AudioTrack track = createAudioTrack(); + + mLcc.addMediaCodec(createAndConfigureMediaCodec()); + mLcc.setAudioTrack(track); + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), + argument.capture()); + assertEquals(argument.getValue().size(), 1); + + mLcc.addMediaCodec(createAndConfigureMediaCodec()); + mLcc.setAudioTrack(null); + verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId())); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void removeAddedMediaCodecAfterSetTrack_callsAudioServiceRemoveCodec() throws Exception { + final AudioTrack track = createAudioTrack(); + final MediaCodec mediaCodec = createAndConfigureMediaCodec(); + + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); + mLcc.removeMediaCodec(mediaCodec); + + verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void addMediaCodecAfterSetTrack_callsAudioServiceAdd() throws Exception { + final AudioTrack track = createAudioTrack(); + + mLcc.addMediaCodec(createAndConfigureMediaCodec()); + mLcc.setAudioTrack(track); + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + + mLcc.addMediaCodec(createAndConfigureMediaCodec()); + verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void removeMediaCodecAfterSetTrack_callsAudioServiceRemove() throws Exception { + final AudioTrack track = createAudioTrack(); + final MediaCodec mediaCodec = createAndConfigureMediaCodec(); + + mLcc.addMediaCodec(mediaCodec); + mLcc.setAudioTrack(track); + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + + mLcc.removeMediaCodec(mediaCodec); + verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + } + + @Test + @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API) + public void removeWrongMediaCodecAfterSetTrack_noAudioServiceRemoveCall() throws Exception { + final AudioTrack track = createAudioTrack(); + + mLcc.addMediaCodec(createAndConfigureMediaCodec()); + mLcc.setAudioTrack(track); + verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList()); + + mLcc.removeMediaCodec(createAndConfigureMediaCodec()); + verify(mAudioService, times(0)).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any()); + } + + private static AudioTrack createAudioTrack() { + return new AudioTrack.Builder() + .setAudioAttributes(new AudioAttributes.Builder().build()) + .setBufferSizeInBytes(TEST_AUDIO_TRACK_BUFFER_SIZE) + .setAudioFormat(new AudioFormat.Builder() + .setChannelMask(TEST_AUDIO_TRACK_CHANNELS) + .setSampleRate(TEST_AUDIO_TRACK_SAMPLERATE).build()) + .build(); + } + + private MediaCodec createAndConfigureMediaCodec() throws Exception { + AssetFileDescriptor testFd = InstrumentationRegistry.getInstrumentation().getContext() + .getResources() + .openRawResourceFd(R.raw.noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4); + + MediaExtractor extractor; + extractor = new MediaExtractor(); + extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(), + testFd.getLength()); + testFd.close(); + + assertEquals("wrong number of tracks", 1, extractor.getTrackCount()); + MediaFormat format = extractor.getTrackFormat(0); + String mime = format.getString(MediaFormat.KEY_MIME); + assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX)); + final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime); + + Log.v(TAG, "configuring with " + format); + mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */); + + return mediaCodec; + } +} diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types index fd785a45a8ba..b7955603a97a 100644 --- a/mime/java-res/android.mime.types +++ b/mime/java-res/android.mime.types @@ -55,6 +55,8 @@ ?application/vnd.android.haptics.vibration+xml ahv ?application/vnd.android.ota ota ?application/vnd.apple.mpegurl m3u8 +?application/vnd.apple.pkpass pkpass +?application/vnd.apple.pkpasses pkpasses ?application/vnd.ms-pki.stl stl ?application/vnd.ms-powerpoint pot ?application/vnd.ms-wpl wpl diff --git a/packages/CrashRecovery/OWNERS b/packages/CrashRecovery/OWNERS new file mode 100644 index 000000000000..daa02111f71f --- /dev/null +++ b/packages/CrashRecovery/OWNERS @@ -0,0 +1,3 @@ +ancr@google.com +harshitmahajan@google.com +robertogil@google.com diff --git a/packages/CrashRecovery/framework/Android.bp b/packages/CrashRecovery/framework/Android.bp new file mode 100644 index 000000000000..b2af315ef2c9 --- /dev/null +++ b/packages/CrashRecovery/framework/Android.bp @@ -0,0 +1,9 @@ +filegroup { + name: "framework-crashrecovery-sources", + srcs: [ + "java/**/*.java", + "java/**/*.aidl", + ], + path: "java", + visibility: ["//frameworks/base:__subpackages__"], +} diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java index 7befbfb0f370..7befbfb0f370 100644 --- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java +++ b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java diff --git a/core/java/android/service/watchdog/IExplicitHealthCheckService.aidl b/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl index 78c0328d36f0..90965092ac2b 100644 --- a/core/java/android/service/watchdog/IExplicitHealthCheckService.aidl +++ b/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl @@ -21,6 +21,7 @@ import android.os.RemoteCallback; /** * @hide */ +@PermissionManuallyEnforced oneway interface IExplicitHealthCheckService { void setCallback(in @nullable RemoteCallback callback); diff --git a/core/java/android/service/watchdog/OWNERS b/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS index 1c045e10c0ec..1c045e10c0ec 100644 --- a/core/java/android/service/watchdog/OWNERS +++ b/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS diff --git a/core/java/android/service/watchdog/PackageConfig.aidl b/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl index 013158676f79..013158676f79 100644 --- a/core/java/android/service/watchdog/PackageConfig.aidl +++ b/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp new file mode 100644 index 000000000000..27ddff93247e --- /dev/null +++ b/packages/CrashRecovery/services/Android.bp @@ -0,0 +1,9 @@ +filegroup { + name: "services-crashrecovery-sources", + srcs: [ + "java/**/*.java", + "java/**/*.aidl", + ], + path: "java", + visibility: ["//frameworks/base:__subpackages__"], +} diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java index 3d610d3747c9..3d610d3747c9 100644 --- a/services/core/java/com/android/server/ExplicitHealthCheckController.java +++ b/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java index d256aead97e8..d256aead97e8 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java diff --git a/services/core/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java index dd543349fc4d..dd543349fc4d 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 2007079ea5ca..2007079ea5ca 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java index f9ef994a523a..f9ef994a523a 100644 --- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java +++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 0d1c9b07bf55..ef218fdd38f3 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -54,7 +54,7 @@ <activity android:name=".v2.ui.InstallLaunch" android:configChanges="orientation|keyboardHidden|screenSize" android:theme="@style/Theme.AlertDialogActivity" - android:exported="true"/> + android:exported="false"/> <activity android:name=".InstallStart" android:theme="@style/Theme.AlertDialogActivity" @@ -140,6 +140,14 @@ </intent-filter> </activity> + <activity android:name=".v2.ui.UninstallLaunch" + android:configChanges="orientation|keyboardHidden|screenSize" + android:theme="@style/Theme.AlertDialogActivity.NoActionBar" + android:excludeFromRecents="true" + android:noHistory="true" + android:exported="false"> + </activity> + <receiver android:name=".UninstallEventReceiver" android:permission="android.permission.INSTALL_PACKAGES" android:exported="false"> @@ -148,6 +156,15 @@ </intent-filter> </receiver> + <receiver android:name=".v2.model.UninstallEventReceiver" + android:permission="android.permission.INSTALL_PACKAGES" + android:exported="false" + android:enabled="false"> + <intent-filter android:priority="1"> + <action android:name="com.android.packageinstaller.ACTION_UNINSTALL_COMMIT" /> + </intent-filter> + </receiver> + <receiver android:name=".PackageInstalledReceiver" android:exported="false"> <intent-filter android:priority="1"> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java index 8d8254ad4058..daedb1aac298 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java @@ -298,14 +298,7 @@ public class InstallInstalling extends Activity { broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - try { - session.commit(pendingIntent.getIntentSender()); - } catch (Exception e) { - Log.e(LOG_TAG, "Cannot install package: ", e); - launchFailure(PackageInstaller.STATUS_FAILURE, - PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); - return; - } + session.commit(pendingIntent.getIntentSender()); mCancelButton.setEnabled(false); setFinishOnTouchOutside(false); } else { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index 9c67817cbef4..34062a4cbde6 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -56,6 +56,7 @@ import com.android.packageinstaller.television.ErrorFragment; import com.android.packageinstaller.television.UninstallAlertFragment; import com.android.packageinstaller.television.UninstallAppProgress; +import com.android.packageinstaller.v2.ui.UninstallLaunch; import java.util.List; /* @@ -80,6 +81,9 @@ public class UninstallerActivity extends Activity { private String mPackageName; private DialogInfo mDialogInfo; + // TODO (sumedhsen): Replace with an Android Feature Flag once implemented + private static final boolean USE_PIA_V2 = false; + @Override public void onCreate(Bundle icicle) { getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); @@ -88,6 +92,20 @@ public class UninstallerActivity extends Activity { // be stale, if e.g. the app was uninstalled while the activity was destroyed. super.onCreate(null); + if (USE_PIA_V2 && !isTv()) { + boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); + Intent piaV2 = new Intent(getIntent()); + piaV2.putExtra(UninstallLaunch.EXTRA_CALLING_PKG_UID, getLaunchedFromUid()); + piaV2.putExtra(UninstallLaunch.EXTRA_CALLING_ACTIVITY_NAME, getCallingActivity()); + if (returnResult) { + piaV2.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + } + piaV2.setClass(this, UninstallLaunch.class); + startActivity(piaV2); + finish(); + return; + } + int callingUid = getLaunchedFromUid(); if (callingUid == Process.INVALID_UID) { // Cannot reach Package/ActivityManager. Aborting uninstall. @@ -176,7 +194,8 @@ public class UninstallerActivity extends Activity { try { mDialogInfo.appInfo = pm.getApplicationInfo(mPackageName, - PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER)); + PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER + | PackageManager.MATCH_ARCHIVED_PACKAGES)); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unable to get packageName. Package manager is dead?"); } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java index 4a93bf80ae39..d113878a3181 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java @@ -189,7 +189,8 @@ public class UninstallAlertDialogFragment extends DialogFragment implements boolean suggestToKeepAppData; try { - PackageInfo pkgInfo = pm.getPackageInfo(pkg, 0); + PackageInfo pkgInfo = pm.getPackageInfo(pkg, + PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ARCHIVED_PACKAGES)); suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData(); } catch (PackageManager.NameNotFoundException e) { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java index 7e7071f5dd24..203af44c9f57 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java @@ -76,6 +76,8 @@ import java.io.IOException; public class InstallRepository { + public static final String EXTRA_STAGED_SESSION_ID = + "com.android.packageinstaller.extra.STAGED_SESSION_ID"; private static final String SCHEME_PACKAGE = "package"; private static final String BROADCAST_ACTION = "com.android.packageinstaller.ACTION_INSTALL_COMMIT"; @@ -142,6 +144,8 @@ public class InstallRepository { ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID) : SessionInfo.INVALID_ID; + mStagedSessionId = mIntent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID); + mCallingPackage = callerInfo.getPackageName(); if (mCallingPackage == null && mSessionId != SessionInfo.INVALID_ID) { @@ -168,7 +172,10 @@ public class InstallRepository { return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); } - if (!isCallerSessionOwner(mPackageInstaller, originatingUid, mSessionId)) { + if ((mSessionId != SessionInfo.INVALID_ID + && !isCallerSessionOwner(mPackageInstaller, originatingUid, mSessionId)) + || (mStagedSessionId != SessionInfo.INVALID_ID + && !isCallerSessionOwner(mPackageInstaller, Process.myUid(), mStagedSessionId))) { return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); } @@ -254,10 +261,11 @@ public class InstallRepository { public void stageForInstall() { Uri uri = mIntent.getData(); - if (mIsSessionInstall || (uri != null && SCHEME_PACKAGE.equals(uri.getScheme()))) { + if (mStagedSessionId != SessionInfo.INVALID_ID + || mIsSessionInstall + || (uri != null && SCHEME_PACKAGE.equals(uri.getScheme()))) { // For a session based install or installing with a package:// URI, there is no file - // for us to stage. Setting the mStagingResult as null will signal InstallViewModel to - // proceed with user confirmation stage. + // for us to stage. mStagingResult.setValue(new InstallReady()); return; } @@ -324,6 +332,10 @@ public class InstallRepository { } } + public int getStagedSessionId() { + return mStagedSessionId; + } + private void cleanupStagingSession() { if (mStagedSessionId > 0) { try { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java index 9c15fd50f216..fe05237bdc57 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java @@ -30,6 +30,8 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; import android.util.Log; import androidx.annotation.NonNull; import java.io.File; @@ -205,9 +207,6 @@ public class PackageUtil { */ public static boolean isCallerSessionOwner(PackageInstaller pi, int originatingUid, int sessionId) { - if (sessionId == SessionInfo.INVALID_ID) { - return false; - } if (originatingUid == Process.ROOT_UID) { return true; } @@ -407,6 +406,24 @@ public class PackageUtil { } /** + * Is a profile part of a user? + * + * @param userManager The user manager + * @param userHandle The handle of the user + * @param profileHandle The handle of the profile + * + * @return If the profile is part of the user or the profile parent of the user + */ + public static boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle, + UserHandle profileHandle) { + if (userHandle.equals(profileHandle)) { + return true; + } + return userManager.getProfileParent(profileHandle) != null + && userManager.getProfileParent(profileHandle).equals(userHandle); + } + + /** * The class to hold an incoming package's icon and label. * See {@link #getAppSnippet(Context, SessionInfo)}, * {@link #getAppSnippet(Context, PackageInfo)}, diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallEventReceiver.java new file mode 100644 index 000000000000..79e00dfdd6df --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallEventReceiver.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import androidx.annotation.NonNull; + +/** + * Receives uninstall events and persists them using a {@link EventResultPersister}. + */ +public class UninstallEventReceiver extends BroadcastReceiver { + private static final Object sLock = new Object(); + private static EventResultPersister sReceiver; + + /** + * Get the event receiver persisting the results + * + * @return The event receiver. + */ + @NonNull private static EventResultPersister getReceiver(@NonNull Context context) { + synchronized (sLock) { + if (sReceiver == null) { + sReceiver = new EventResultPersister( + TemporaryFileManager.getUninstallStateFile(context)); + } + } + + return sReceiver; + } + + @Override + public void onReceive(Context context, Intent intent) { + getReceiver(context).onEventReceived(context, intent); + } + + /** + * Add an observer. If there is already an event for this id, call back inside of this call. + * + * @param context A context of the current app + * @param id The id the observer is for or {@code GENERATE_NEW_ID} to generate a new one. + * @param observer The observer to call back. + * + * @return The id for this event + */ + public static int addObserver(@NonNull Context context, int id, + @NonNull EventResultPersister.EventResultObserver observer) + throws EventResultPersister.OutOfIdsException { + return getReceiver(context).addObserver(id, observer); + } + + /** + * Remove a observer. + * + * @param context A context of the current app + * @param id The id the observer was added for + */ + static void removeObserver(@NonNull Context context, int id) { + getReceiver(context).removeObserver(id); + } + + /** + * @param context A context of the current app + * + * @return A new uninstall id + */ + static int getNewId(@NonNull Context context) throws EventResultPersister.OutOfIdsException { + return getReceiver(context).getNewId(); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java new file mode 100644 index 000000000000..2e43b75e5123 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; +import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; +import static com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionForUid; +import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid; +import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted; +import static com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame; +import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_APP_UNAVAILABLE; +import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_GENERIC_ERROR; +import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED; + +import android.Manifest; +import android.app.Activity; +import android.app.AppOpsManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.admin.DevicePolicyManager; +import android.app.usage.StorageStats; +import android.app.usage.StorageStatsManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.UninstallCompleteCallback; +import android.content.pm.VersionedPackage; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.MutableLiveData; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallFailed; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallReady; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallSuccess; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired; +import java.io.IOException; +import java.util.List; + +public class UninstallRepository { + + private static final String TAG = UninstallRepository.class.getSimpleName(); + private static final String UNINSTALL_FAILURE_CHANNEL = "uninstall_failure"; + private static final String BROADCAST_ACTION = + "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"; + + private static final String EXTRA_UNINSTALL_ID = + "com.android.packageinstaller.extra.UNINSTALL_ID"; + private static final String EXTRA_APP_LABEL = + "com.android.packageinstaller.extra.APP_LABEL"; + private static final String EXTRA_IS_CLONE_APP = + "com.android.packageinstaller.extra.IS_CLONE_APP"; + private static final String EXTRA_PACKAGE_NAME = + "com.android.packageinstaller.extra.EXTRA_PACKAGE_NAME"; + + private final Context mContext; + private final AppOpsManager mAppOpsManager; + private final PackageManager mPackageManager; + private final UserManager mUserManager; + private final NotificationManager mNotificationManager; + private final MutableLiveData<UninstallStage> mUninstallResult = new MutableLiveData<>(); + public UserHandle mUninstalledUser; + public UninstallCompleteCallback mCallback; + private ApplicationInfo mTargetAppInfo; + private ActivityInfo mTargetActivityInfo; + private Intent mIntent; + private CharSequence mTargetAppLabel; + private String mTargetPackageName; + private String mCallingActivity; + private boolean mUninstallFromAllUsers; + private boolean mIsClonedApp; + private int mUninstallId; + + public UninstallRepository(Context context) { + mContext = context; + mAppOpsManager = context.getSystemService(AppOpsManager.class); + mPackageManager = context.getPackageManager(); + mUserManager = context.getSystemService(UserManager.class); + mNotificationManager = context.getSystemService(NotificationManager.class); + } + + public UninstallStage performPreUninstallChecks(Intent intent, CallerInfo callerInfo) { + mIntent = intent; + + int callingUid = callerInfo.getUid(); + mCallingActivity = callerInfo.getActivityName(); + + if (callingUid == Process.INVALID_UID) { + Log.e(TAG, "Could not determine the launching uid."); + return new UninstallAborted(ABORT_REASON_GENERIC_ERROR); + // TODO: should we give any indication to the user? + } + + String callingPackage = getPackageNameForUid(mContext, callingUid, null); + if (callingPackage == null) { + Log.e(TAG, "Package not found for originating uid " + callingUid); + return new UninstallAborted(ABORT_REASON_GENERIC_ERROR); + } else { + if (mAppOpsManager.noteOpNoThrow( + AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage) + != MODE_ALLOWED) { + Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps"); + return new UninstallAborted(ABORT_REASON_GENERIC_ERROR); + } + } + + if (getMaxTargetSdkVersionForUid(mContext, callingUid) >= Build.VERSION_CODES.P + && !isPermissionGranted(mContext, Manifest.permission.REQUEST_DELETE_PACKAGES, + callingUid) + && !isPermissionGranted(mContext, Manifest.permission.DELETE_PACKAGES, callingUid)) { + Log.e(TAG, "Uid " + callingUid + " does not have " + + Manifest.permission.REQUEST_DELETE_PACKAGES + " or " + + Manifest.permission.DELETE_PACKAGES); + + return new UninstallAborted(ABORT_REASON_GENERIC_ERROR); + } + + // Get intent information. + // We expect an intent with URI of the form package:<packageName>#<className> + // className is optional; if specified, it is the activity the user chose to uninstall + final Uri packageUri = intent.getData(); + if (packageUri == null) { + Log.e(TAG, "No package URI in intent"); + return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE); + } + mTargetPackageName = packageUri.getEncodedSchemeSpecificPart(); + if (mTargetPackageName == null) { + Log.e(TAG, "Invalid package name in URI: " + packageUri); + return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE); + } + + mUninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, + false); + if (mUninstallFromAllUsers && !mUserManager.isAdminUser()) { + Log.e(TAG, "Only admin user can request uninstall for all users"); + return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED); + } + + mUninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class); + if (mUninstalledUser == null) { + mUninstalledUser = Process.myUserHandle(); + } else { + List<UserHandle> profiles = mUserManager.getUserProfiles(); + if (!profiles.contains(mUninstalledUser)) { + Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall " + + "for user " + mUninstalledUser); + return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED); + } + } + + mCallback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK, + PackageManager.UninstallCompleteCallback.class); + + try { + mTargetAppInfo = mPackageManager.getApplicationInfo(mTargetPackageName, + PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER)); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to get packageName"); + } + + if (mTargetAppInfo == null) { + Log.e(TAG, "Invalid packageName: " + mTargetPackageName); + return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE); + } + + // The class name may have been specified (e.g. when deleting an app from all apps) + final String className = packageUri.getFragment(); + if (className != null) { + try { + mTargetActivityInfo = mPackageManager.getActivityInfo( + new ComponentName(mTargetPackageName, className), + PackageManager.ComponentInfoFlags.of(0)); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to get className"); + // Continue as the ActivityInfo isn't critical. + } + } + + return new UninstallReady(); + } + + public UninstallStage generateUninstallDetails() { + UninstallUserActionRequired.Builder uarBuilder = new UninstallUserActionRequired.Builder(); + StringBuilder messageBuilder = new StringBuilder(); + + mTargetAppLabel = mTargetAppInfo.loadSafeLabel(mPackageManager); + + // If the Activity label differs from the App label, then make sure the user + // knows the Activity belongs to the App being uninstalled. + if (mTargetActivityInfo != null) { + final CharSequence activityLabel = mTargetActivityInfo.loadSafeLabel(mPackageManager); + if (CharSequence.compare(activityLabel, mTargetAppLabel) != 0) { + messageBuilder.append( + mContext.getString(R.string.uninstall_activity_text, activityLabel)); + messageBuilder.append(" ").append(mTargetAppLabel).append(".\n\n"); + } + } + + final boolean isUpdate = + (mTargetAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + final UserHandle myUserHandle = Process.myUserHandle(); + boolean isSingleUser = isSingleUser(); + + if (isUpdate) { + messageBuilder.append(mContext.getString( + isSingleUser ? R.string.uninstall_update_text : + R.string.uninstall_update_text_multiuser)); + } else if (mUninstallFromAllUsers && !isSingleUser) { + messageBuilder.append(mContext.getString( + R.string.uninstall_application_text_all_users)); + } else if (!mUninstalledUser.equals(myUserHandle)) { + // Uninstalling user is issuing uninstall for another user + UserManager customUserManager = mContext.createContextAsUser(mUninstalledUser, 0) + .getSystemService(UserManager.class); + String userName = customUserManager.getUserName(); + + String uninstalledUserType = getUninstalledUserType(myUserHandle, mUninstalledUser); + String messageString; + if (USER_TYPE_PROFILE_MANAGED.equals(uninstalledUserType)) { + messageString = mContext.getString( + R.string.uninstall_application_text_current_user_work_profile, userName); + } else if (USER_TYPE_PROFILE_CLONE.equals(uninstalledUserType)) { + mIsClonedApp = true; + messageString = mContext.getString( + R.string.uninstall_application_text_current_user_clone_profile); + } else { + messageString = mContext.getString( + R.string.uninstall_application_text_user, userName); + } + messageBuilder.append(messageString); + } else if (isCloneProfile(mUninstalledUser)) { + mIsClonedApp = true; + messageBuilder.append(mContext.getString( + R.string.uninstall_application_text_current_user_clone_profile)); + } else if (myUserHandle.equals(UserHandle.SYSTEM) + && hasClonedInstance(mTargetAppInfo.packageName)) { + messageBuilder.append(mContext.getString( + R.string.uninstall_application_text_with_clone_instance, mTargetAppLabel)); + } else { + messageBuilder.append(mContext.getString(R.string.uninstall_application_text)); + } + + uarBuilder.setMessage(messageBuilder.toString()); + + if (mIsClonedApp) { + uarBuilder.setTitle(mContext.getString(R.string.cloned_app_label, mTargetAppLabel)); + } else { + uarBuilder.setTitle(mTargetAppLabel.toString()); + } + + boolean suggestToKeepAppData = false; + try { + PackageInfo pkgInfo = mPackageManager.getPackageInfo(mTargetPackageName, 0); + suggestToKeepAppData = + pkgInfo.applicationInfo != null && pkgInfo.applicationInfo.hasFragileUserData(); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Cannot check hasFragileUserData for " + mTargetPackageName, e); + } + + long appDataSize = 0; + if (suggestToKeepAppData) { + appDataSize = getAppDataSize(mTargetPackageName, + mUninstallFromAllUsers ? null : mUninstalledUser); + } + uarBuilder.setAppDataSize(appDataSize); + + return uarBuilder.build(); + } + + /** + * Returns whether there is only one "full" user on this device. + * + * <p><b>Note:</b> on devices that use {@link android.os.UserManager#isHeadlessSystemUserMode() + * headless system user mode}, the system user is not "full", so it's not be considered in the + * calculation.</p> + */ + private boolean isSingleUser() { + final int userCount = mUserManager.getUserCount(); + return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2); + } + + /** + * Returns the type of the user from where an app is being uninstalled. We are concerned with + * only USER_TYPE_PROFILE_MANAGED and USER_TYPE_PROFILE_CLONE and whether the user and profile + * belong to the same profile group. + */ + @Nullable + private String getUninstalledUserType(UserHandle myUserHandle, + UserHandle uninstalledUserHandle) { + if (!mUserManager.isSameProfileGroup(myUserHandle, uninstalledUserHandle)) { + return null; + } + + UserManager customUserManager = mContext.createContextAsUser(uninstalledUserHandle, 0) + .getSystemService(UserManager.class); + String[] userTypes = {USER_TYPE_PROFILE_MANAGED, USER_TYPE_PROFILE_CLONE}; + for (String userType : userTypes) { + if (customUserManager.isUserOfType(userType)) { + return userType; + } + } + return null; + } + + private boolean hasClonedInstance(String packageName) { + // Check if clone user is present on the device. + UserHandle cloneUser = null; + List<UserHandle> profiles = mUserManager.getUserProfiles(); + for (UserHandle userHandle : profiles) { + if (!userHandle.equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) { + cloneUser = userHandle; + break; + } + } + // Check if another instance of given package exists in clone user profile. + try { + return cloneUser != null + && mPackageManager.getPackageUidAsUser(packageName, + PackageManager.PackageInfoFlags.of(0), cloneUser.getIdentifier()) > 0; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + private boolean isCloneProfile(UserHandle userHandle) { + UserManager customUserManager = mContext.createContextAsUser(userHandle, 0) + .getSystemService(UserManager.class); + return customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE); + } + + /** + * Get number of bytes of the app data of the package. + * + * @param pkg The package that might have app data. + * @param user The user the package belongs to or {@code null} if files of all users should + * be counted. + * @return The number of bytes. + */ + private long getAppDataSize(@NonNull String pkg, @Nullable UserHandle user) { + if (user != null) { + return getAppDataSizeForUser(pkg, user); + } + // We are uninstalling from all users. Get cumulative app data size for all users. + List<UserHandle> userHandles = mUserManager.getUserHandles(true); + long totalAppDataSize = 0; + int numUsers = userHandles.size(); + for (int i = 0; i < numUsers; i++) { + totalAppDataSize += getAppDataSizeForUser(pkg, userHandles.get(i)); + } + return totalAppDataSize; + } + + /** + * Get number of bytes of the app data of the package. + * + * @param pkg The package that might have app data. + * @param user The user the package belongs to + * @return The number of bytes. + */ + private long getAppDataSizeForUser(@NonNull String pkg, @NonNull UserHandle user) { + StorageStatsManager storageStatsManager = + mContext.getSystemService(StorageStatsManager.class); + try { + StorageStats stats = storageStatsManager.queryStatsForPackage( + mPackageManager.getApplicationInfo(pkg, 0).storageUuid, pkg, user); + return stats.getDataBytes(); + } catch (PackageManager.NameNotFoundException | IOException | SecurityException e) { + Log.e(TAG, "Cannot determine amount of app data for " + pkg, e); + } + return 0; + } + + public void initiateUninstall(boolean keepData) { + // Get an uninstallId to track results and show a notification on non-TV devices. + try { + mUninstallId = UninstallEventReceiver.addObserver(mContext, + EventResultPersister.GENERATE_NEW_ID, this::handleUninstallResult); + } catch (EventResultPersister.OutOfIdsException e) { + Log.e(TAG, "Failed to start uninstall", e); + handleUninstallResult(PackageInstaller.STATUS_FAILURE, + PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0); + return; + } + + // TODO: Check with UX whether to show UninstallUninstalling dialog / notification? + mUninstallResult.setValue(new UninstallUninstalling(mTargetAppLabel, mIsClonedApp)); + + Bundle uninstallData = new Bundle(); + uninstallData.putInt(EXTRA_UNINSTALL_ID, mUninstallId); + uninstallData.putString(EXTRA_PACKAGE_NAME, mTargetPackageName); + uninstallData.putBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS, mUninstallFromAllUsers); + uninstallData.putCharSequence(EXTRA_APP_LABEL, mTargetAppLabel); + uninstallData.putBoolean(EXTRA_IS_CLONE_APP, mIsClonedApp); + Log.i(TAG, "Uninstalling extras = " + uninstallData); + + // Get a PendingIntent for result broadcast and issue an uninstall request + Intent broadcastIntent = new Intent(BROADCAST_ACTION); + broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId); + broadcastIntent.setPackage(mContext.getPackageName()); + + PendingIntent pendingIntent = + PendingIntent.getBroadcast(mContext, mUninstallId, broadcastIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + + if (!startUninstall(mTargetPackageName, mUninstalledUser, pendingIntent, + mUninstallFromAllUsers, keepData)) { + handleUninstallResult(PackageInstaller.STATUS_FAILURE, + PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0); + } + } + + private void handleUninstallResult(int status, int legacyStatus, @Nullable String message, + int serviceId) { + if (mCallback != null) { + // The caller will be informed about the result via a callback + mCallback.onUninstallComplete(mTargetPackageName, legacyStatus, message); + + // Since the caller already received the results, just finish the app at this point + mUninstallResult.setValue(null); + return; + } + + boolean returnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); + if (returnResult || mCallingActivity != null) { + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus); + + if (status == PackageInstaller.STATUS_SUCCESS) { + UninstallSuccess.Builder successBuilder = new UninstallSuccess.Builder() + .setResultIntent(intent) + .setActivityResultCode(Activity.RESULT_OK); + mUninstallResult.setValue(successBuilder.build()); + } else { + UninstallFailed.Builder failedBuilder = new UninstallFailed.Builder(true) + .setResultIntent(intent) + .setActivityResultCode(Activity.RESULT_FIRST_USER); + mUninstallResult.setValue(failedBuilder.build()); + } + return; + } + + // Caller did not want the result back. So, we either show a Toast, or a Notification. + if (status == PackageInstaller.STATUS_SUCCESS) { + UninstallSuccess.Builder successBuilder = new UninstallSuccess.Builder() + .setActivityResultCode(legacyStatus) + .setMessage(mIsClonedApp + ? mContext.getString(R.string.uninstall_done_clone_app, mTargetAppLabel) + : mContext.getString(R.string.uninstall_done_app, mTargetAppLabel)); + mUninstallResult.setValue(successBuilder.build()); + } else { + UninstallFailed.Builder failedBuilder = new UninstallFailed.Builder(false); + Notification.Builder uninstallFailedNotification = null; + + NotificationChannel uninstallFailureChannel = new NotificationChannel( + UNINSTALL_FAILURE_CHANNEL, + mContext.getString(R.string.uninstall_failure_notification_channel), + NotificationManager.IMPORTANCE_DEFAULT); + mNotificationManager.createNotificationChannel(uninstallFailureChannel); + + uninstallFailedNotification = new Notification.Builder(mContext, + UNINSTALL_FAILURE_CHANNEL); + + UserHandle myUserHandle = Process.myUserHandle(); + switch (legacyStatus) { + case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER -> { + // Find out if the package is an active admin for some non-current user. + UserHandle otherBlockingUserHandle = + findUserOfDeviceAdmin(myUserHandle, mTargetPackageName); + + if (otherBlockingUserHandle == null) { + Log.d(TAG, "Uninstall failed because " + mTargetPackageName + + " is a device admin"); + + addDeviceManagerButton(mContext, uninstallFailedNotification); + setBigText(uninstallFailedNotification, mContext.getString( + R.string.uninstall_failed_device_policy_manager)); + } else { + Log.d(TAG, "Uninstall failed because " + mTargetPackageName + + " is a device admin of user " + otherBlockingUserHandle); + + String userName = + mContext.createContextAsUser(otherBlockingUserHandle, 0) + .getSystemService(UserManager.class).getUserName(); + setBigText(uninstallFailedNotification, String.format( + mContext.getString( + R.string.uninstall_failed_device_policy_manager_of_user), + userName)); + } + } + case PackageManager.DELETE_FAILED_OWNER_BLOCKED -> { + UserHandle otherBlockingUserHandle = findBlockingUser(mTargetPackageName); + boolean isProfileOfOrSame = isProfileOfOrSame(mUserManager, myUserHandle, + otherBlockingUserHandle); + + if (isProfileOfOrSame) { + addDeviceManagerButton(mContext, uninstallFailedNotification); + } else { + addManageUsersButton(mContext, uninstallFailedNotification); + } + + String bigText = null; + if (otherBlockingUserHandle == null) { + Log.d(TAG, "Uninstall failed for " + mTargetPackageName + + " with code " + status + " no blocking user"); + } else if (otherBlockingUserHandle == UserHandle.SYSTEM) { + bigText = mContext.getString( + R.string.uninstall_blocked_device_owner); + } else { + bigText = mContext.getString(mUninstallFromAllUsers ? + R.string.uninstall_all_blocked_profile_owner + : R.string.uninstall_blocked_profile_owner); + } + if (bigText != null) { + setBigText(uninstallFailedNotification, bigText); + } + } + default -> { + Log.d(TAG, "Uninstall blocked for " + mTargetPackageName + + " with legacy code " + legacyStatus); + } + } + + uninstallFailedNotification.setContentTitle( + mContext.getString(R.string.uninstall_failed_app, mTargetAppLabel)); + uninstallFailedNotification.setOngoing(false); + uninstallFailedNotification.setSmallIcon(R.drawable.ic_error); + failedBuilder.setUninstallNotification(mUninstallId, + uninstallFailedNotification.build()); + + mUninstallResult.setValue(failedBuilder.build()); + } + } + + /** + * @param myUserHandle {@link UserHandle} of the current user. + * @param packageName Name of the package being uninstalled. + * @return the {@link UserHandle} of the user in which a package is a device admin. + */ + @Nullable + private UserHandle findUserOfDeviceAdmin(UserHandle myUserHandle, String packageName) { + for (UserHandle otherUserHandle : mUserManager.getUserHandles(true)) { + // We only catch the case when the user in question is neither the + // current user nor its profile. + if (isProfileOfOrSame(mUserManager, myUserHandle, otherUserHandle)) { + continue; + } + DevicePolicyManager dpm = mContext.createContextAsUser(otherUserHandle, 0) + .getSystemService(DevicePolicyManager.class); + if (dpm.packageHasActiveAdmins(packageName)) { + return otherUserHandle; + } + } + return null; + } + + /** + * + * @param packageName Name of the package being uninstalled. + * @return {@link UserHandle} of the user in which a package is blocked from being uninstalled. + */ + @Nullable + private UserHandle findBlockingUser(String packageName) { + for (UserHandle otherUserHandle : mUserManager.getUserHandles(true)) { + // TODO (b/307399586): Add a negation when the logic of the method + // is fixed + if (mPackageManager.canUserUninstall(packageName, otherUserHandle)) { + return otherUserHandle; + } + } + return null; + } + + /** + * Set big text for the notification. + * + * @param builder The builder of the notification + * @param text The text to set. + */ + private void setBigText(@NonNull Notification.Builder builder, + @NonNull CharSequence text) { + builder.setStyle(new Notification.BigTextStyle().bigText(text)); + } + + /** + * Add a button to the notification that links to the user management. + * + * @param context The context the notification is created in + * @param builder The builder of the notification + */ + private void addManageUsersButton(@NonNull Context context, + @NonNull Notification.Builder builder) { + builder.addAction((new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_settings_multiuser), + context.getString(R.string.manage_users), + PendingIntent.getActivity(context, 0, getUserSettingsIntent(), + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))).build()); + } + + private Intent getUserSettingsIntent() { + Intent intent = new Intent(Settings.ACTION_USER_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK); + return intent; + } + + /** + * Add a button to the notification that links to the device policy management. + * + * @param context The context the notification is created in + * @param builder The builder of the notification + */ + private void addDeviceManagerButton(@NonNull Context context, + @NonNull Notification.Builder builder) { + builder.addAction((new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_lock), + context.getString(R.string.manage_device_administrators), + PendingIntent.getActivity(context, 0, getDeviceManagerIntent(), + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))).build()); + } + + private Intent getDeviceManagerIntent() { + Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.Settings$DeviceAdminSettingsActivity"); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK); + return intent; + } + + /** + * Starts an uninstall for the given package. + * + * @return {@code true} if there was no exception while uninstalling. This does not represent + * the result of the uninstall. Result will be made available in + * {@link #handleUninstallResult(int, int, String, int)} + */ + private boolean startUninstall(String packageName, UserHandle targetUser, + PendingIntent pendingIntent, boolean uninstallFromAllUsers, boolean keepData) { + int flags = uninstallFromAllUsers ? PackageManager.DELETE_ALL_USERS : 0; + flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0; + try { + mContext.createContextAsUser(targetUser, 0) + .getPackageManager().getPackageInstaller().uninstall( + new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), + flags, pendingIntent.getIntentSender()); + return true; + } catch (IllegalArgumentException e) { + Log.e(TAG, "Failed to uninstall", e); + return false; + } + } + + public void cancelInstall() { + if (mCallback != null) { + mCallback.onUninstallComplete(mTargetPackageName, + PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user"); + } + } + + public MutableLiveData<UninstallStage> getUninstallResult() { + return mUninstallResult; + } + + public static class CallerInfo { + + private final String mActivityName; + private final int mUid; + + public CallerInfo(String activityName, int uid) { + mActivityName = activityName; + mUid = uid; + } + + public String getActivityName() { + return mActivityName; + } + + public int getUid() { + return mUid; + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java new file mode 100644 index 000000000000..9aea6b18214b --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model.uninstallstagedata; + +import android.app.Activity; +import com.android.packageinstaller.R; + +public class UninstallAborted extends UninstallStage { + + public static final int ABORT_REASON_GENERIC_ERROR = 0; + public static final int ABORT_REASON_APP_UNAVAILABLE = 1; + public static final int ABORT_REASON_USER_NOT_ALLOWED = 2; + private final int mStage = UninstallStage.STAGE_ABORTED; + private final int mAbortReason; + private final int mDialogTitleResource; + private final int mDialogTextResource; + private final int mActivityResultCode = Activity.RESULT_FIRST_USER; + + public UninstallAborted(int abortReason) { + mAbortReason = abortReason; + switch (abortReason) { + case ABORT_REASON_APP_UNAVAILABLE -> { + mDialogTitleResource = R.string.app_not_found_dlg_title; + mDialogTextResource = R.string.app_not_found_dlg_text; + } + case ABORT_REASON_USER_NOT_ALLOWED -> { + mDialogTitleResource = 0; + mDialogTextResource = R.string.user_is_not_allowed_dlg_text; + } + default -> { + mDialogTitleResource = 0; + mDialogTextResource = R.string.generic_error_dlg_text; + } + } + } + + public int getAbortReason() { + return mAbortReason; + } + + public int getActivityResultCode() { + return mActivityResultCode; + } + + public int getDialogTitleResource() { + return mDialogTitleResource; + } + + public int getDialogTextResource() { + return mDialogTextResource; + } + + @Override + public int getStageCode() { + return mStage; + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java new file mode 100644 index 000000000000..6ed8883570e3 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model.uninstallstagedata; + +import android.app.Activity; +import android.app.Notification; +import android.content.Intent; + +public class UninstallFailed extends UninstallStage { + + private final int mStage = UninstallStage.STAGE_FAILED; + private final boolean mReturnResult; + /** + * If the caller wants the result back, the intent will hold the uninstall failure status code + * and legacy code. + */ + private final Intent mResultIntent; + /** + * When the user does not request a result back, this notification will be shown indicating the + * reason for uninstall failure. + */ + private final Notification mUninstallNotification; + /** + * ID used to show {@link #mUninstallNotification} + */ + private final int mUninstallId; + private final int mActivityResultCode; + + public UninstallFailed(boolean returnResult, Intent resultIntent, int activityResultCode, + int uninstallId, Notification uninstallNotification) { + mReturnResult = returnResult; + mResultIntent = resultIntent; + mActivityResultCode = activityResultCode; + mUninstallId = uninstallId; + mUninstallNotification = uninstallNotification; + } + + public boolean returnResult() { + return mReturnResult; + } + + public Intent getResultIntent() { + return mResultIntent; + } + + public int getActivityResultCode() { + return mActivityResultCode; + } + + public Notification getUninstallNotification() { + return mUninstallNotification; + } + + public int getUninstallId() { + return mUninstallId; + } + + @Override + public int getStageCode() { + return mStage; + } + + public static class Builder { + + private final boolean mReturnResult; + private int mActivityResultCode = Activity.RESULT_CANCELED; + /** + * See {@link UninstallFailed#mResultIntent} + */ + private Intent mResultIntent = null; + /** + * See {@link UninstallFailed#mUninstallNotification} + */ + private Notification mUninstallNotification; + /** + * See {@link UninstallFailed#mUninstallId} + */ + private int mUninstallId; + + public Builder(boolean returnResult) { + mReturnResult = returnResult; + } + + public Builder setUninstallNotification(int uninstallId, Notification notification) { + mUninstallId = uninstallId; + mUninstallNotification = notification; + return this; + } + + public Builder setResultIntent(Intent intent) { + mResultIntent = intent; + return this; + } + + public Builder setActivityResultCode(int resultCode) { + mActivityResultCode = resultCode; + return this; + } + + public UninstallFailed build() { + return new UninstallFailed(mReturnResult, mResultIntent, mActivityResultCode, + mUninstallId, mUninstallNotification); + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java new file mode 100644 index 000000000000..0108cb471b5a --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model.uninstallstagedata; + +public class UninstallReady extends UninstallStage { + + private final int mStage = UninstallStage.STAGE_READY; + + @Override + public int getStageCode() { + return mStage; + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java new file mode 100644 index 000000000000..87ca4ec37349 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java @@ -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.packageinstaller.v2.model.uninstallstagedata; + +public abstract class UninstallStage { + + public static final int STAGE_DEFAULT = -1; + public static final int STAGE_ABORTED = 0; + public static final int STAGE_READY = 1; + public static final int STAGE_USER_ACTION_REQUIRED = 2; + public static final int STAGE_UNINSTALLING = 3; + public static final int STAGE_SUCCESS = 4; + public static final int STAGE_FAILED = 5; + + public abstract int getStageCode(); +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java new file mode 100644 index 000000000000..5df6b020cef5 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model.uninstallstagedata; + +import android.content.Intent; + +public class UninstallSuccess extends UninstallStage { + + private final int mStage = UninstallStage.STAGE_SUCCESS; + private final String mMessage; + private final Intent mResultIntent; + private final int mActivityResultCode; + + public UninstallSuccess(Intent resultIntent, int activityResultCode, String message) { + mResultIntent = resultIntent; + mActivityResultCode = activityResultCode; + mMessage = message; + } + + public String getMessage() { + return mMessage; + } + + public Intent getResultIntent() { + return mResultIntent; + } + + public int getActivityResultCode() { + return mActivityResultCode; + } + + @Override + public int getStageCode() { + return mStage; + } + + public static class Builder { + + private Intent mResultIntent; + private int mActivityResultCode; + private String mMessage; + + public Builder() { + } + + public Builder setResultIntent(Intent intent) { + mResultIntent = intent; + return this; + } + + public Builder setActivityResultCode(int resultCode) { + mActivityResultCode = resultCode; + return this; + } + + public Builder setMessage(String message) { + mMessage = message; + return this; + } + + public UninstallSuccess build() { + return new UninstallSuccess(mResultIntent, mActivityResultCode, mMessage); + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java new file mode 100644 index 000000000000..f5156cb676e9 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model.uninstallstagedata; + +public class UninstallUninstalling extends UninstallStage { + + private final int mStage = UninstallStage.STAGE_UNINSTALLING; + + private final CharSequence mAppLabel; + private final boolean mIsCloneUser; + + public UninstallUninstalling(CharSequence appLabel, boolean isCloneUser) { + mAppLabel = appLabel; + mIsCloneUser = isCloneUser; + } + + public CharSequence getAppLabel() { + return mAppLabel; + } + + public boolean isCloneUser() { + return mIsCloneUser; + } + + @Override + public int getStageCode() { + return mStage; + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java new file mode 100644 index 000000000000..b6001493ade9 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model.uninstallstagedata; + +public class UninstallUserActionRequired extends UninstallStage { + + private final int mStage = UninstallStage.STAGE_USER_ACTION_REQUIRED; + private final String mTitle; + private final String mMessage; + private final long mAppDataSize; + + public UninstallUserActionRequired(String title, String message, long appDataSize) { + mTitle = title; + mMessage = message; + mAppDataSize = appDataSize; + } + + public String getTitle() { + return mTitle; + } + + public String getMessage() { + return mMessage; + } + + public long getAppDataSize() { + return mAppDataSize; + } + + @Override + public int getStageCode() { + return mStage; + } + + public static class Builder { + + private String mTitle; + private String mMessage; + private long mAppDataSize = 0; + + public Builder setTitle(String title) { + mTitle = title; + return this; + } + + public Builder setMessage(String message) { + mMessage = message; + return this; + } + + public Builder setAppDataSize(long appDataSize) { + mAppDataSize = appDataSize; + return this; + } + + public UninstallUserActionRequired build() { + return new UninstallUserActionRequired(mTitle, mMessage, mAppDataSize); + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java index 949355535b64..d06b4b3b1336 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java @@ -19,8 +19,7 @@ package com.android.packageinstaller.v2.ui; import static android.content.Intent.CATEGORY_LAUNCHER; import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY; import static android.os.Process.INVALID_UID; -import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR; -import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY; +import static com.android.packageinstaller.v2.model.InstallRepository.EXTRA_STAGED_SESSION_ID; import android.app.Activity; import android.app.AppOpsManager; @@ -108,55 +107,68 @@ public class InstallLaunch extends FragmentActivity implements InstallActionList * Main controller of the UI. This method shows relevant dialogs based on the install stage */ private void onInstallStageChange(InstallStage installStage) { - if (installStage.getStageCode() == InstallStage.STAGE_STAGING) { - InstallStagingFragment stagingDialog = new InstallStagingFragment(); - showDialogInner(stagingDialog); - mInstallViewModel.getStagingProgress().observe(this, stagingDialog::setProgress); - } else if (installStage.getStageCode() == InstallStage.STAGE_ABORTED) { - InstallAborted aborted = (InstallAborted) installStage; - switch (aborted.getAbortReason()) { - // TODO: check if any dialog is to be shown for ABORT_REASON_INTERNAL_ERROR - case InstallAborted.ABORT_REASON_DONE, InstallAborted.ABORT_REASON_INTERNAL_ERROR -> - setResult(aborted.getActivityResultCode(), aborted.getResultIntent(), true); - case InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted); - default -> setResult(RESULT_CANCELED, null, true); + switch (installStage.getStageCode()) { + case InstallStage.STAGE_STAGING -> { + InstallStagingFragment stagingDialog = new InstallStagingFragment(); + showDialogInner(stagingDialog); + mInstallViewModel.getStagingProgress().observe(this, stagingDialog::setProgress); } - } else if (installStage.getStageCode() == InstallStage.STAGE_USER_ACTION_REQUIRED) { - InstallUserActionRequired uar = (InstallUserActionRequired) installStage; - switch (uar.getActionReason()) { - case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION: - InstallConfirmationFragment actionDialog = new InstallConfirmationFragment(uar); - showDialogInner(actionDialog); - break; - case InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE: - ExternalSourcesBlockedFragment externalSourceDialog = - new ExternalSourcesBlockedFragment(uar); - showDialogInner(externalSourceDialog); - break; - case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE: - AnonymousSourceFragment anonymousSourceDialog = new AnonymousSourceFragment(); - showDialogInner(anonymousSourceDialog); + case InstallStage.STAGE_ABORTED -> { + InstallAborted aborted = (InstallAborted) installStage; + switch (aborted.getAbortReason()) { + // TODO: check if any dialog is to be shown for ABORT_REASON_INTERNAL_ERROR + case InstallAborted.ABORT_REASON_DONE, + InstallAborted.ABORT_REASON_INTERNAL_ERROR -> + setResult(aborted.getActivityResultCode(), aborted.getResultIntent(), true); + case InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted); + default -> setResult(RESULT_CANCELED, null, true); + } } - } else if (installStage.getStageCode() == InstallStage.STAGE_INSTALLING) { - InstallInstalling installing = (InstallInstalling) installStage; - InstallInstallingFragment installingDialog = new InstallInstallingFragment(installing); - showDialogInner(installingDialog); - } else if (installStage.getStageCode() == InstallStage.STAGE_SUCCESS) { - InstallSuccess success = (InstallSuccess) installStage; - if (success.shouldReturnResult()) { - Intent successIntent = success.getResultIntent(); - setResult(Activity.RESULT_OK, successIntent, true); - } else { - InstallSuccessFragment successFragment = new InstallSuccessFragment(success); - showDialogInner(successFragment); + case InstallStage.STAGE_USER_ACTION_REQUIRED -> { + InstallUserActionRequired uar = (InstallUserActionRequired) installStage; + switch (uar.getActionReason()) { + case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION -> { + InstallConfirmationFragment actionDialog = + new InstallConfirmationFragment(uar); + showDialogInner(actionDialog); + } + case InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE -> { + ExternalSourcesBlockedFragment externalSourceDialog = + new ExternalSourcesBlockedFragment(uar); + showDialogInner(externalSourceDialog); + } + case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE -> { + AnonymousSourceFragment anonymousSourceDialog = + new AnonymousSourceFragment(); + showDialogInner(anonymousSourceDialog); + } + } + } + case InstallStage.STAGE_INSTALLING -> { + InstallInstalling installing = (InstallInstalling) installStage; + InstallInstallingFragment installingDialog = + new InstallInstallingFragment(installing); + showDialogInner(installingDialog); + } + case InstallStage.STAGE_SUCCESS -> { + InstallSuccess success = (InstallSuccess) installStage; + if (success.shouldReturnResult()) { + Intent successIntent = success.getResultIntent(); + setResult(Activity.RESULT_OK, successIntent, true); + } else { + InstallSuccessFragment successFragment = new InstallSuccessFragment(success); + showDialogInner(successFragment); + } + } + case InstallStage.STAGE_FAILED -> { + InstallFailed failed = (InstallFailed) installStage; + InstallFailedFragment failedDialog = new InstallFailedFragment(failed); + showDialogInner(failedDialog); + } + default -> { + Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode()); + showDialogInner(null); } - } else if (installStage.getStageCode() == InstallStage.STAGE_FAILED) { - InstallFailed failed = (InstallFailed) installStage; - InstallFailedFragment failedDialog = new InstallFailedFragment(failed); - showDialogInner(failedDialog); - } else { - Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode()); - showDialogInner(null); } } @@ -325,10 +337,16 @@ public class InstallLaunch extends FragmentActivity implements InstallActionList } new Handler(Looper.getMainLooper()).postDelayed(() -> { if (!isDestroyed()) { - // Bring Pia to the foreground. FLAG_ACTIVITY_REORDER_TO_FRONT will reuse the - // paused instance, so we don't unnecessarily create a new instance of Pia. + // Relaunch Pia to continue installation. startActivity(getIntent() - .addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)); + .putExtra(EXTRA_STAGED_SESSION_ID, mInstallViewModel.getStagedSessionId())); + + // If the userId of the root of activity stack is different from current userId, + // starting Pia again lead to duplicate instances of the app in the stack. + // As such, finish the old instance. Old Pia is finished even if the userId of + // the root is the same, since there is no way to determine the difference in + // userIds. + finish(); } }, 500); } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java new file mode 100644 index 000000000000..b8a93559d782 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java @@ -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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.ui; + +public interface UninstallActionListener { + + void onPositiveResponse(boolean keepData); + + void onNegativeResponse(); +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java new file mode 100644 index 000000000000..7638e917c7d5 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.ui; + +import static android.os.Process.INVALID_UID; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + +import android.app.Activity; +import android.app.NotificationManager; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.ViewModelProvider; +import com.android.packageinstaller.v2.model.UninstallRepository; +import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallFailed; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallSuccess; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired; +import com.android.packageinstaller.v2.ui.fragments.UninstallConfirmationFragment; +import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment; +import com.android.packageinstaller.v2.ui.fragments.UninstallUninstallingFragment; +import com.android.packageinstaller.v2.viewmodel.UninstallViewModel; +import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory; + +public class UninstallLaunch extends FragmentActivity implements UninstallActionListener { + + public static final String EXTRA_CALLING_PKG_UID = + UninstallLaunch.class.getPackageName() + ".callingPkgUid"; + public static final String EXTRA_CALLING_ACTIVITY_NAME = + UninstallLaunch.class.getPackageName() + ".callingActivityName"; + public static final String TAG = UninstallLaunch.class.getSimpleName(); + private static final String TAG_DIALOG = "dialog"; + + private UninstallViewModel mUninstallViewModel; + private UninstallRepository mUninstallRepository; + private FragmentManager mFragmentManager; + private NotificationManager mNotificationManager; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + + // Never restore any state, esp. never create any fragments. The data in the fragment might + // be stale, if e.g. the app was uninstalled while the activity was destroyed. + super.onCreate(null); + + mFragmentManager = getSupportFragmentManager(); + mNotificationManager = getSystemService(NotificationManager.class); + + mUninstallRepository = new UninstallRepository(getApplicationContext()); + mUninstallViewModel = new ViewModelProvider(this, + new UninstallViewModelFactory(this.getApplication(), mUninstallRepository)).get( + UninstallViewModel.class); + + Intent intent = getIntent(); + CallerInfo callerInfo = new CallerInfo( + intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME), + intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID)); + mUninstallViewModel.preprocessIntent(intent, callerInfo); + + mUninstallViewModel.getCurrentUninstallStage().observe(this, + this::onUninstallStageChange); + } + + /** + * Main controller of the UI. This method shows relevant dialogs / fragments based on the + * uninstall stage + */ + private void onUninstallStageChange(UninstallStage uninstallStage) { + if (uninstallStage.getStageCode() == UninstallStage.STAGE_ABORTED) { + UninstallAborted aborted = (UninstallAborted) uninstallStage; + if (aborted.getAbortReason() == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE || + aborted.getAbortReason() == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED) { + UninstallErrorFragment errorDialog = new UninstallErrorFragment(aborted); + showDialogInner(errorDialog); + } else { + setResult(aborted.getActivityResultCode(), null, true); + } + } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_USER_ACTION_REQUIRED) { + UninstallUserActionRequired uar = (UninstallUserActionRequired) uninstallStage; + UninstallConfirmationFragment confirmationDialog = new UninstallConfirmationFragment( + uar); + showDialogInner(confirmationDialog); + } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_UNINSTALLING) { + // TODO: This shows a fragment whether or not user requests a result or not. + // Originally, if the user does not request a result, we used to show a notification. + // And a fragment if the user requests a result back. Should we consolidate and + // show a fragment always? + UninstallUninstalling uninstalling = (UninstallUninstalling) uninstallStage; + UninstallUninstallingFragment uninstallingDialog = new UninstallUninstallingFragment( + uninstalling); + showDialogInner(uninstallingDialog); + } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_FAILED) { + UninstallFailed failed = (UninstallFailed) uninstallStage; + if (!failed.returnResult()) { + mNotificationManager.notify(failed.getUninstallId(), + failed.getUninstallNotification()); + } + setResult(failed.getActivityResultCode(), failed.getResultIntent(), true); + } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_SUCCESS) { + UninstallSuccess success = (UninstallSuccess) uninstallStage; + if (success.getMessage() != null) { + Toast.makeText(this, success.getMessage(), Toast.LENGTH_LONG).show(); + } + setResult(success.getActivityResultCode(), success.getResultIntent(), true); + } else { + Log.e(TAG, "Invalid stage: " + uninstallStage.getStageCode()); + showDialogInner(null); + } + } + + /** + * Replace any visible dialog by the dialog returned by InstallRepository + * + * @param newDialog The new dialog to display + */ + private void showDialogInner(DialogFragment newDialog) { + DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag( + TAG_DIALOG); + if (currentDialog != null) { + currentDialog.dismissAllowingStateLoss(); + } + if (newDialog != null) { + newDialog.show(mFragmentManager, TAG_DIALOG); + } + } + + public void setResult(int resultCode, Intent data, boolean shouldFinish) { + super.setResult(resultCode, data); + if (shouldFinish) { + finish(); + } + } + + @Override + public void onPositiveResponse(boolean keepData) { + mUninstallViewModel.initiateUninstall(keepData); + } + + @Override + public void onNegativeResponse() { + mUninstallViewModel.cancelInstall(); + setResult(Activity.RESULT_FIRST_USER, null, true); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java new file mode 100644 index 000000000000..1b0885ea684a --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.ui.fragments; + +import static android.text.format.Formatter.formatFileSize; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.View; +import android.widget.CheckBox; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired; +import com.android.packageinstaller.v2.ui.UninstallActionListener; + +/** + * Dialog to show while requesting user confirmation for uninstalling an app. + */ +public class UninstallConfirmationFragment extends DialogFragment { + + private final UninstallUserActionRequired mDialogData; + private UninstallActionListener mUninstallActionListener; + + private CheckBox mKeepData; + + public UninstallConfirmationFragment(UninstallUserActionRequired dialogData) { + mDialogData = dialogData; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mUninstallActionListener = (UninstallActionListener) context; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()) + .setTitle(mDialogData.getTitle()) + .setPositiveButton(R.string.ok, + (dialogInt, which) -> mUninstallActionListener.onPositiveResponse( + mKeepData != null && mKeepData.isChecked())) + .setNegativeButton(R.string.cancel, + (dialogInt, which) -> mUninstallActionListener.onNegativeResponse()); + + long appDataSize = mDialogData.getAppDataSize(); + if (appDataSize == 0) { + builder.setMessage(mDialogData.getMessage()); + } else { + View dialogView = getLayoutInflater().inflate(R.layout.uninstall_content_view, null); + + ((TextView) dialogView.requireViewById(R.id.message)).setText(mDialogData.getMessage()); + mKeepData = dialogView.requireViewById(R.id.keepData); + mKeepData.setVisibility(View.VISIBLE); + mKeepData.setText(getString(R.string.uninstall_keep_data, + formatFileSize(getContext(), appDataSize))); + + builder.setView(dialogView); + } + return builder.create(); + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + super.onCancel(dialog); + mUninstallActionListener.onNegativeResponse(); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java new file mode 100644 index 000000000000..305daba14f26 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.ui.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted; +import com.android.packageinstaller.v2.ui.UninstallActionListener; + +/** + * Dialog to show when an app cannot be uninstalled + */ +public class UninstallErrorFragment extends DialogFragment { + + private final UninstallAborted mDialogData; + private UninstallActionListener mUninstallActionListener; + + public UninstallErrorFragment(UninstallAborted dialogData) { + mDialogData = dialogData; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mUninstallActionListener = (UninstallActionListener) context; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()) + .setMessage(mDialogData.getDialogTextResource()) + .setNegativeButton(R.string.ok, + (dialogInt, which) -> mUninstallActionListener.onNegativeResponse()); + + if (mDialogData.getDialogTitleResource() != 0) { + builder.setTitle(mDialogData.getDialogTitleResource()); + } + return builder.create(); + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + super.onCancel(dialog); + mUninstallActionListener.onNegativeResponse(); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java new file mode 100644 index 000000000000..23cc421890ac --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.ui.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling; + +/** + * Dialog to show that the app is uninstalling. + */ +public class UninstallUninstallingFragment extends DialogFragment { + + UninstallUninstalling mDialogData; + + public UninstallUninstallingFragment(UninstallUninstalling dialogData) { + mDialogData = dialogData; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()) + .setCancelable(false); + if (mDialogData.isCloneUser()) { + builder.setTitle(requireContext().getString(R.string.uninstalling_cloned_app, + mDialogData.getAppLabel())); + } else { + builder.setTitle(requireContext().getString(R.string.uninstalling_app, + mDialogData.getAppLabel())); + } + Dialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(false); + + return dialog; + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java index 759f4689996f..04a0622627b9 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java @@ -98,4 +98,8 @@ public class InstallViewModel extends AndroidViewModel { } }); } + + public int getStagedSessionId() { + return mRepository.getStagedSessionId(); + } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java new file mode 100644 index 000000000000..3f7bce8f85d0 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.viewmodel; + +import android.app.Application; +import android.content.Intent; +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.MediatorLiveData; +import androidx.lifecycle.MutableLiveData; +import com.android.packageinstaller.v2.model.UninstallRepository; +import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo; +import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage; + +public class UninstallViewModel extends AndroidViewModel { + + private static final String TAG = UninstallViewModel.class.getSimpleName(); + private final UninstallRepository mRepository; + private final MediatorLiveData<UninstallStage> mCurrentUninstallStage = + new MediatorLiveData<>(); + + public UninstallViewModel(@NonNull Application application, UninstallRepository repository) { + super(application); + mRepository = repository; + } + + public MutableLiveData<UninstallStage> getCurrentUninstallStage() { + return mCurrentUninstallStage; + } + + public void preprocessIntent(Intent intent, CallerInfo callerInfo) { + UninstallStage stage = mRepository.performPreUninstallChecks(intent, callerInfo); + if (stage.getStageCode() != UninstallStage.STAGE_ABORTED) { + stage = mRepository.generateUninstallDetails(); + } + mCurrentUninstallStage.setValue(stage); + } + + public void initiateUninstall(boolean keepData) { + mRepository.initiateUninstall(keepData); + // Since uninstall is an async operation, we will get the uninstall result later in time. + // Result of the uninstall will be set in UninstallRepository#mUninstallResult. + // As such, mCurrentUninstallStage will need to add another MutableLiveData + // as a data source + mCurrentUninstallStage.addSource(mRepository.getUninstallResult(), uninstallStage -> { + if (uninstallStage != null) { + mCurrentUninstallStage.setValue(uninstallStage); + } + }); + } + + public void cancelInstall() { + mRepository.cancelInstall(); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java new file mode 100644 index 000000000000..cd9845e2cfad --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.viewmodel; + +import android.app.Application; +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import com.android.packageinstaller.v2.model.UninstallRepository; + +public class UninstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory { + + private final UninstallRepository mRepository; + private final Application mApplication; + + public UninstallViewModelFactory(Application application, UninstallRepository repository) { + // Calling super class' ctor ensures that create method is called correctly and the right + // ctor of UninstallViewModel is used. If we fail to do that, the default ctor: + // UninstallViewModel(application) is used, and repository isn't initialized in + // the viewmodel + super(application); + mApplication = application; + mRepository = repository; + } + + @NonNull + @Override + @SuppressWarnings("unchecked") + public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { + return (T) new UninstallViewModel(mApplication, mRepository); + } +} diff --git a/packages/PrintSpooler/res/values-hi/strings.xml b/packages/PrintSpooler/res/values-hi/strings.xml index 4f2719f8e0f8..e42ef390f048 100644 --- a/packages/PrintSpooler/res/values-hi/strings.xml +++ b/packages/PrintSpooler/res/values-hi/strings.xml @@ -20,9 +20,9 @@ <string name="more_options_button" msgid="2243228396432556771">"ज़्यादा विकल्प"</string> <string name="label_destination" msgid="9132510997381599275">"गंतव्य"</string> <string name="label_copies" msgid="3634531042822968308">"प्रतियां"</string> - <string name="label_copies_summary" msgid="3861966063536529540">"प्रतियां:"</string> + <string name="label_copies_summary" msgid="3861966063536529540">"कॉपी:"</string> <string name="label_paper_size" msgid="908654383827777759">"काग़ज़ का आकार"</string> - <string name="label_paper_size_summary" msgid="5668204981332138168">"काग़ज़ का आकार:"</string> + <string name="label_paper_size_summary" msgid="5668204981332138168">"काग़ज़ का साइज़:"</string> <string name="label_color" msgid="1108690305218188969">"रंग"</string> <string name="label_duplex" msgid="5370037254347072243">"दो-तरफ़ा"</string> <string name="label_orientation" msgid="2853142581990496477">"स्क्रीन की दिशा"</string> diff --git a/packages/SettingsLib/ProfileSelector/res/values/styles.xml b/packages/SettingsLib/ProfileSelector/res/values/styles.xml index 0b703c99884b..365dcb255852 100644 --- a/packages/SettingsLib/ProfileSelector/res/values/styles.xml +++ b/packages/SettingsLib/ProfileSelector/res/values/styles.xml @@ -37,5 +37,6 @@ <item name="tabIndicatorAnimationDuration">0</item> <item name="tabTextAppearance">@style/SettingsLibTabsTextAppearance</item> <item name="tabTextColor">@color/settingslib_tabs_text_color</item> + <item name="tabRippleColor">@android:color/transparent</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties b/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties index 83d7549551ce..23fdc013acdd 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties +++ b/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties @@ -12,4 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -sdk=NEWEST_SDK
\ No newline at end of file +sdk=NEWEST_SDK +graphicsMode=NATIVE diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java deleted file mode 100644 index 45210abad88e..000000000000 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java +++ /dev/null @@ -1,20 +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. - */ - -@GraphicsMode(GraphicsMode.Mode.NATIVE) -package com.android.settingslib.spa.screenshot.widget.ui; - -import org.robolectric.annotation.GraphicsMode; diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt index 223e99e61204..56796945f015 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt @@ -72,7 +72,8 @@ private fun UserManager.getUserGroups(): List<UserGroup> { private fun UserManager.showInSettings(userInfo: UserInfo): Int { val userProperties = getUserProperties(userInfo.userHandle) - return if (userInfo.isQuietModeEnabled && userProperties.hideInSettingsInQuietMode) { + return if (userInfo.isQuietModeEnabled && userProperties.showInQuietMode + == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) { UserProperties.SHOW_IN_SETTINGS_NO } else { userProperties.showInSettings diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 3dc39430fd21..6e127716c589 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -567,7 +567,7 @@ <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string> <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Forbundet via ARC"</string> <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Forbundet via eARC"</string> - <string name="tv_media_transfer_default" msgid="38102257053315304">"Fjernsyn som standardoutput"</string> + <string name="tv_media_transfer_default" msgid="38102257053315304">"Fjernsyn som standard"</string> <string name="tv_media_transfer_hdmi" msgid="2229000864416329818">"HDMI-udgang"</string> <string name="tv_media_transfer_internal_speakers" msgid="2433193551482972117">"Interne højttalere"</string> <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Der kunne ikke oprettes forbindelse. Sluk og tænd enheden"</string> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index a2cafee03880..e5c8242d5cad 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -437,8 +437,8 @@ <string name="transcode_notification" msgid="5560515979793436168">"Kuva transkodeerimise märguanded"</string> <string name="transcode_disable_cache" msgid="3160069309377467045">"Transkodeerimise vahemälu keelamine"</string> <string name="widevine_settings_title" msgid="4023329801172572917">"Widevine\'i seaded"</string> - <string name="force_l3_fallback_title" msgid="4987972688770202547">"L3 varuvariandiks sundimine"</string> - <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Valige, kas sundida L3 varuvariandiks"</string> + <string name="force_l3_fallback_title" msgid="4987972688770202547">"Sundtaane tasemele L3"</string> + <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Valige sundtaandeks tasemele L3"</string> <string name="runningservices_settings_title" msgid="6460099290493086515">"Käitatud teenused"</string> <string name="runningservices_settings_summary" msgid="1046080643262665743">"Praegu käitatud teenuste vaatamine ja juhtimine"</string> <string name="select_webview_provider_title" msgid="3917815648099445503">"WebView\' rakendamine"</string> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index 7b71639380c7..8f5c04290c4c 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -634,7 +634,7 @@ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"ಅತಿಥಿ ಚಟುವಟಿಕೆಯನ್ನು ಉಳಿಸಬೇಕೆ?"</string> <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"ನೀವು ಪ್ರಸ್ತುತ ಸೆಶನ್ನ ಚಟುವಟಿಕೆಯನ್ನು ಉಳಿಸಬಹುದು ಅಥವಾ ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಬಹುದು"</string> <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ಅಳಿಸಿ"</string> - <string name="guest_exit_save_data_button" msgid="3690974510644963547">"ಉಳಿಸಿ"</string> + <string name="guest_exit_save_data_button" msgid="3690974510644963547">"ಸೇವ್ ಮಾಡಿ"</string> <string name="guest_exit_button" msgid="5774985819191803960">"ಅತಿಥಿ ಮೋಡ್ನಿಂದ ನಿರ್ಗಮಿಸಿ"</string> <string name="guest_reset_button" msgid="2515069346223503479">"ಅತಿಥಿ ಸೆಷನ್ ಅನ್ನು ರೀಸೆಟ್ ಮಾಡಿ"</string> <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"ಅತಿಥಿ ಮೋಡ್ನಿಂದ ನಿರ್ಗಮಿಸಿ"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index 2ef4620d6e67..75dbbf1aeb72 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -437,8 +437,8 @@ <string name="transcode_notification" msgid="5560515979793436168">"Прикажувај известувања за транскодирање"</string> <string name="transcode_disable_cache" msgid="3160069309377467045">"Оневозможи го кешот на транскодирањето"</string> <string name="widevine_settings_title" msgid="4023329801172572917">"Поставки за Widevine"</string> - <string name="force_l3_fallback_title" msgid="4987972688770202547">"Резервен план за Force L3"</string> - <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Изберете за да се овозможи резервен план за Force L3"</string> + <string name="force_l3_fallback_title" msgid="4987972688770202547">"Наметни алтернативно безбедносно ниво L3"</string> + <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Изберете за да се наметне алтернативно безбедносно ниво L3"</string> <string name="runningservices_settings_title" msgid="6460099290493086515">"Активни услуги"</string> <string name="runningservices_settings_summary" msgid="1046080643262665743">"Погледнете и контролирајте услуги што се моментално активни"</string> <string name="select_webview_provider_title" msgid="3917815648099445503">"Примена на WebView"</string> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index 65e4b2e97c28..185870a9e467 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -683,7 +683,7 @@ <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ଇଥରନେଟ୍।"</string> <string name="accessibility_no_calling" msgid="3540827068323895748">"କୌଣସି କଲିଂ ନାହିଁ।"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"ଏକ ପ୍ରୋଫାଇଲ ଛବି ବାଛନ୍ତୁ"</string> - <string name="default_user_icon_description" msgid="6554047177298972638">"ଡିଫଲ୍ଟ ଉପଯୋଗକର୍ତ୍ତା ଆଇକନ"</string> + <string name="default_user_icon_description" msgid="6554047177298972638">"ଡିଫଲ୍ଟ ୟୁଜର ଆଇକନ"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"ଫିଜିକାଲ କୀବୋର୍ଡ"</string> <string name="keyboard_layout_dialog_title" msgid="3927180147005616290">"କୀବୋର୍ଡ ଲେଆଉଟ ବାଛନ୍ତୁ"</string> <string name="keyboard_layout_default_label" msgid="1997292217218546957">"ଡିଫଲ୍ଟ"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index fb870c4e679c..e96c49fbbad6 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -437,8 +437,8 @@ <string name="transcode_notification" msgid="5560515979793436168">"Hiện thông báo chuyển mã"</string> <string name="transcode_disable_cache" msgid="3160069309377467045">"Vô hiệu hóa bộ nhớ đệm dùng để chuyển mã"</string> <string name="widevine_settings_title" msgid="4023329801172572917">"Cài đặt Widevine"</string> - <string name="force_l3_fallback_title" msgid="4987972688770202547">"Buộc chuyển sang phương án dự phòng L3"</string> - <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Chọn để buộc chuyển sang phương án dự phòng L3"</string> + <string name="force_l3_fallback_title" msgid="4987972688770202547">"Buộc sử dụng mức bảo mật L3"</string> + <string name="force_l3_fallback_summary" msgid="3080790841069996016">"Chọn để buộc sử dụng mức bảo mật L3"</string> <string name="runningservices_settings_title" msgid="6460099290493086515">"Các dịch vụ đang chạy"</string> <string name="runningservices_settings_summary" msgid="1046080643262665743">"Xem và kiểm soát các dịch vụ đang chạy"</string> <string name="select_webview_provider_title" msgid="3917815648099445503">"Triển khai WebView"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 1a5acf650cb4..5aa2bfc6441a 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1373,11 +1373,11 @@ <string name="tv_media_transfer_earc_subtitle">Connected via eARC</string> <!-- TV media output switcher. Title for the default audio output of the device [CHAR LIMIT=NONE] --> - <string name="tv_media_transfer_default">TV Default</string> + <string name="tv_media_transfer_default">TV default</string> <!-- TV media output switcher. Subtitle for default audio output which is HDMI, e.g. TV dongle [CHAR LIMIT=NONE] --> - <string name="tv_media_transfer_hdmi">HDMI Output</string> + <string name="tv_media_transfer_hdmi">HDMI output</string> <!-- TV media output switcher. Subtitle for default audio output which is internal speaker, i.e. panel VTs [CHAR LIMIT=NONE] --> - <string name="tv_media_transfer_internal_speakers">Internal Speakers</string> + <string name="tv_media_transfer_internal_speakers">Internal speakers</string> <!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] --> <string name="profile_connect_timeout_subtext">Problem connecting. Turn device off & back on</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java index 83c106b18a6b..92db50878a70 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.provider.Settings; +import android.os.UserManager; import android.util.ArraySet; import android.view.accessibility.AccessibilityManager; @@ -68,4 +69,10 @@ public final class BatteryUtils { } return packageNames; } + + /** Returns true if current user is a work profile user. */ + public static boolean isWorkProfile(Context context) { + final UserManager userManager = context.getSystemService(UserManager.class); + return userManager.isManagedProfile() && !userManager.isSystemUser(); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 5dacba5357cd..52b51d7e42e9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -413,12 +413,8 @@ public abstract class InfoMediaManager extends MediaManager { */ @NonNull List<MediaDevice> getSelectedMediaDevices() { - if (TextUtils.isEmpty(mPackageName)) { - Log.w(TAG, "getSelectedMediaDevices() package name is null or empty!"); - return Collections.emptyList(); - } + RoutingSessionInfo info = getRoutingSessionInfo(); - final RoutingSessionInfo info = getRoutingSessionInfo(); if (info == null) { Log.w(TAG, "getSelectedMediaDevices() cannot find selectable MediaDevice from : " + mPackageName); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java index 3514932d4e8d..02ec90d968b1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputConstants.java @@ -48,6 +48,17 @@ public class MediaOutputConstants { "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG"; /** + * An intent action to launch a media output dialog without any app or playback metadata, which + * only controls system routing. + * + * <p>System routes are those provided by the system, such as built-in speakers, wired headsets, + * bluetooth devices, and other outputs that require the app to feed media samples to the + * framework. + */ + public static final String ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG = + "com.android.systemui.action.LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG"; + + /** * An intent action to launch media output broadcast dialog. */ public static final String ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG = diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java index faccf2f15f49..90140d4540be 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ProviderTileTest.java @@ -259,13 +259,6 @@ public class ProviderTileTest { } @Test - public void isSearchable_noMetadata_isTrue() { - final Tile tile = new ProviderTile(mProviderInfo, "category", null); - - assertThat(tile.isSearchable()).isTrue(); - } - - @Test public void isSearchable_notSet_isTrue() { final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java index c3e0c0b3b79c..6424352d2c1b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java @@ -29,6 +29,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.UserManager; import android.provider.Settings; import android.view.accessibility.AccessibilityManager; @@ -40,6 +41,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; +import org.robolectric.Shadows; import org.robolectric.shadows.ShadowAccessibilityManager; import java.util.Arrays; @@ -99,6 +101,20 @@ public class BatteryUtilsTest { .containsExactly(DEFAULT_TTS_PACKAGE, ACCESSIBILITY_PACKAGE); } + @Test + public void isWorkProfile_defaultValue_returnFalse() { + assertThat(BatteryUtils.isWorkProfile(mContext)).isFalse(); + } + + @Test + public void isWorkProfile_workProfileMode_returnTrue() { + final UserManager userManager = mContext.getSystemService(UserManager.class); + Shadows.shadowOf(userManager).setManagedProfile(true); + Shadows.shadowOf(userManager).setIsSystemUser(false); + + assertThat(BatteryUtils.isWorkProfile(mContext)).isTrue(); + } + private void setTtsPackageName(String defaultTtsPackageName) { Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_SYNTH, defaultTtsPackageName); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java index 0cabab241be4..542f1010c492 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java @@ -168,10 +168,10 @@ public class LicenseHtmlGeneratorFromXmlTest { private static final String EXPECTED_NEW_HTML_STRING = HTML_HEAD_STRING + HTML_NEW_BODY_STRING; private static final String EXPECTED_OLD_HTML_STRING_WITH_CUSTOM_HEADING = - HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_OLD_BODY_STRING; + HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n<br/>\n" + HTML_OLD_BODY_STRING; private static final String EXPECTED_NEW_HTML_STRING_WITH_CUSTOM_HEADING = - HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_NEW_BODY_STRING; + HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n<br/>\n" + HTML_NEW_BODY_STRING; @Test public void testParseValidXmlStream() throws XmlPullParserException, IOException { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java index 721e69d2eb8d..f0f53d6e82ad 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java @@ -39,9 +39,9 @@ import android.widget.TextView; import androidx.annotation.ColorRes; import androidx.preference.PreferenceViewHolder; -import com.android.settingslib.widget.preference.banner.R; import com.android.settingslib.testutils.OverpoweredReflectionHelper; +import com.android.settingslib.widget.preference.banner.R; import org.junit.Before; import org.junit.Test; diff --git a/packages/Shell/res/values-kn/strings.xml b/packages/Shell/res/values-kn/strings.xml index a6f61ed1c93e..56448f73d9c2 100644 --- a/packages/Shell/res/values-kn/strings.xml +++ b/packages/Shell/res/values-kn/strings.xml @@ -42,6 +42,6 @@ <string name="bugreport_info_name" msgid="4414036021935139527">"ಫೈಲ್ಹೆಸರು"</string> <string name="bugreport_info_title" msgid="2306030793918239804">"ಬಗ್ ಶೀರ್ಷಿಕೆ"</string> <string name="bugreport_info_description" msgid="5072835127481627722">"ಬಗ್ ಸಾರಾಂಶ"</string> - <string name="save" msgid="4781509040564835759">"ಉಳಿಸಿ"</string> + <string name="save" msgid="4781509040564835759">"ಸೇವ್ ಮಾಡಿ"</string> <string name="bugreport_intent_chooser_title" msgid="7605709494790894076">"ಬಗ್ ವರದಿಯನ್ನು ಹಂಚು"</string> </resources> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index e218308758ab..0a71cda4d6dd 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -1044,6 +1044,7 @@ android:exported="true"> <intent-filter android:priority="1"> <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" /> + <action android:name="com.android.systemui.action.LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG" /> <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG" /> <action android:name="com.android.systemui.action.DISMISS_MEDIA_OUTPUT_DIALOG" /> </intent-filter> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml index cc6638bcf574..6192d4ae1269 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml @@ -9,7 +9,7 @@ <string name="power_label" msgid="7699720321491287839">"電源"</string> <string name="power_utterance" msgid="7444296686402104807">"電源オプション"</string> <string name="recent_apps_label" msgid="6583276995616385847">"最近使ったアプリ"</string> - <string name="lockscreen_label" msgid="648347953557887087">"ロック画面"</string> + <string name="lockscreen_label" msgid="648347953557887087">"画面をロック"</string> <string name="quick_settings_label" msgid="2999117381487601865">"クイック設定"</string> <string name="notifications_label" msgid="6829741046963013567">"通知"</string> <string name="screenshot_label" msgid="863978141223970162">"スクリーンショット"</string> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a745ab5cbdd9..17620651128f 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -114,6 +114,13 @@ flag { } flag { + name: "unfold_animation_background_progress" + namespace: "systemui" + description: "Moves unfold animation progress calculation to a background thread" + bug: "277879146" +} + +flag { name: "qs_new_pipeline" namespace: "systemui" description: "Use the new pipeline for Quick Settings. Should have no behavior changes." @@ -143,8 +150,17 @@ flag { } flag { + name: "theme_overlay_controller_wakefulness_deprecation" + namespace: "systemui" + description: "Replacing WakefulnessLifecycle by KeyguardTransitionInteractor in " + "ThemOverlayController to mitigate flickering when locking the device" + bug: "308676488" +} + +flag { name: "media_in_scene_container" namespace: "systemui" description: "Enable media in the scene container framework" bug: "296122467" } + diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt index ddc3d3a31e94..1860c9f1656e 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt @@ -18,8 +18,8 @@ package com.android.systemui.scene import android.app.AlertDialog import android.content.Context +import com.android.systemui.bouncer.ui.composable.BouncerDialogFactory import com.android.systemui.bouncer.ui.composable.BouncerScene -import com.android.systemui.bouncer.ui.composable.BouncerSceneDialogFactory import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.Scene @@ -38,8 +38,8 @@ interface BouncerSceneModule { @Provides @SysUISingleton - fun bouncerSceneDialogFactory(@Application context: Context): BouncerSceneDialogFactory { - return object : BouncerSceneDialogFactory { + fun bouncerSceneDialogFactory(@Application context: Context): BouncerDialogFactory { + return object : BouncerDialogFactory { override fun invoke(): AlertDialog { return SystemUIDialog(context) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt new file mode 100644 index 000000000000..ba80a8deffb7 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -0,0 +1,734 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 android.app.AlertDialog +import android.app.Dialog +import android.content.DialogInterface +import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.snap +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +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.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.times +import com.android.compose.PlatformButton +import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.transitions +import com.android.compose.modifiers.thenIf +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 +import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel +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.fold.ui.composable.foldPosture +import com.android.systemui.fold.ui.helper.FoldPosture +import com.android.systemui.res.R +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.pow + +@Composable +fun BouncerContent( + viewModel: BouncerViewModel, + dialogFactory: BouncerDialogFactory, + modifier: Modifier +) { + val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible + val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() + val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) + + when (layout) { + BouncerSceneLayout.STANDARD -> + StandardLayout( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = modifier, + ) + BouncerSceneLayout.SIDE_BY_SIDE -> + SideBySideLayout( + viewModel = viewModel, + dialogFactory = dialogFactory, + isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, + modifier = modifier, + ) + BouncerSceneLayout.STACKED -> + StackedLayout( + viewModel = viewModel, + dialogFactory = dialogFactory, + isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, + modifier = modifier, + ) + BouncerSceneLayout.SPLIT -> + SplitLayout( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = modifier, + ) + } +} + +/** + * Renders the contents of the actual bouncer UI, the area that takes user input to do an + * authentication attempt, including all messaging UI (directives, reasoning, errors, etc.). + */ +@Composable +private fun StandardLayout( + viewModel: BouncerViewModel, + dialogFactory: BouncerDialogFactory, + modifier: Modifier = Modifier, + outputOnly: Boolean = false, +) { + val foldPosture: FoldPosture by foldPosture() + val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState() + val isSplitAroundTheFold = + foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired + val currentSceneKey = + if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey + + SceneTransitionLayout( + currentScene = currentSceneKey, + onChangeScene = {}, + transitions = SceneTransitions, + modifier = modifier, + ) { + scene(SceneKeys.ContiguousSceneKey) { + FoldSplittable( + viewModel = viewModel, + dialogFactory = dialogFactory, + outputOnly = outputOnly, + isSplit = false, + ) + } + + scene(SceneKeys.SplitSceneKey) { + FoldSplittable( + viewModel = viewModel, + dialogFactory = dialogFactory, + outputOnly = outputOnly, + isSplit = true, + ) + } + } +} + +/** + * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user + * switcher UI) and laid out vertically, centered horizontally. + * + * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't + * render across the location of the fold hardware when the device is fully or part-way unfolded + * with the fold hinge in a horizontal position. + * + * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN + * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter + * their PIN or pattern. + */ +@Composable +private fun SceneScope.FoldSplittable( + viewModel: BouncerViewModel, + dialogFactory: BouncerDialogFactory, + outputOnly: Boolean, + isSplit: Boolean, + modifier: Modifier = Modifier, +) { + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() + val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } + val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() + val splitRatio = + LocalContext.current.resources.getFloat( + R.dimen.motion_layout_half_fold_bouncer_height_ratio + ) + + Column(modifier = modifier.padding(horizontal = 32.dp)) { + // Content above the fold, when split on a foldable device in a "table top" posture: + Box( + modifier = + Modifier.element(SceneElements.AboveFold).fillMaxWidth().thenIf(isSplit) { + Modifier.weight(splitRatio) + }, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth().padding(top = 92.dp), + ) { + Crossfade( + targetState = message, + label = "Bouncer message", + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + ) { message -> + Text( + text = message.text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + ) + } + + Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) + + UserInputArea( + viewModel = viewModel, + visibility = UserInputAreaVisibility.OUTPUT_ONLY, + ) + } + } + + // Content below the fold, when split on a foldable device in a "table top" posture: + Box( + modifier = + Modifier.element(SceneElements.BelowFold).fillMaxWidth().thenIf(isSplit) { + Modifier.weight(1 - splitRatio) + }, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + if (!outputOnly) { + Box(Modifier.weight(1f)) { + UserInputArea( + viewModel = viewModel, + visibility = UserInputAreaVisibility.INPUT_ONLY, + modifier = Modifier.align(Alignment.Center), + ) + } + } + + Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) + + val actionButtonModifier = Modifier.height(56.dp) + + actionButton.let { actionButtonViewModel -> + if (actionButtonViewModel != null) { + BouncerActionButton( + viewModel = actionButtonViewModel, + modifier = actionButtonModifier, + ) + } else { + Spacer(modifier = actionButtonModifier) + } + } + + Spacer(Modifier.height(48.dp)) + } + } + + if (dialogMessage != null) { + if (dialog == null) { + dialog = + dialogFactory().apply { + setMessage(dialogMessage) + setButton( + DialogInterface.BUTTON_NEUTRAL, + context.getString(R.string.ok), + ) { _, _ -> + viewModel.onThrottlingDialogDismissed() + } + setCancelable(false) + setCanceledOnTouchOutside(false) + show() + } + } + } else { + dialog?.dismiss() + dialog = null + } + } +} + +/** + * Renders the user input area, where the user interacts with the UI to enter their credentials. + * + * For example, this can be the pattern input area, the password text box, or pin pad. + */ +@Composable +private fun UserInputArea( + viewModel: BouncerViewModel, + visibility: UserInputAreaVisibility, + modifier: Modifier = Modifier, +) { + val authMethodViewModel: AuthMethodBouncerViewModel? by + viewModel.authMethodViewModel.collectAsState() + + when (val nonNullViewModel = authMethodViewModel) { + is PinBouncerViewModel -> + when (visibility) { + UserInputAreaVisibility.OUTPUT_ONLY -> + PinInputDisplay( + viewModel = nonNullViewModel, + modifier = modifier, + ) + UserInputAreaVisibility.INPUT_ONLY -> + PinPad( + viewModel = nonNullViewModel, + modifier = modifier, + ) + } + is PasswordBouncerViewModel -> + if (visibility == UserInputAreaVisibility.INPUT_ONLY) { + PasswordBouncer( + viewModel = nonNullViewModel, + modifier = modifier, + ) + } + is PatternBouncerViewModel -> + if (visibility == UserInputAreaVisibility.INPUT_ONLY) { + PatternBouncer( + viewModel = nonNullViewModel, + modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false) + ) + } + else -> Unit + } +} + +/** + * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call. + */ +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun BouncerActionButton( + viewModel: BouncerActionButtonModel, + modifier: Modifier = Modifier, +) { + Button( + onClick = viewModel.onClick, + modifier = + modifier.thenIf(viewModel.onLongClick != null) { + Modifier.combinedClickable( + onClick = viewModel.onClick, + onLongClick = viewModel.onLongClick, + ) + }, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.onTertiaryContainer, + ), + ) { + Text( + text = viewModel.label, + style = MaterialTheme.typography.bodyMedium, + ) + } +} + +/** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */ +@Composable +private fun UserSwitcher( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val selectedUserImage by viewModel.selectedUserImage.collectAsState(null) + val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList()) + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = modifier, + ) { + selectedUserImage?.let { + Image( + bitmap = it.asImageBitmap(), + contentDescription = null, + modifier = Modifier.size(SelectedUserImageSize), + ) + } + + val (isDropdownExpanded, setDropdownExpanded) = remember { mutableStateOf(false) } + + dropdownItems.firstOrNull()?.let { firstDropdownItem -> + Spacer(modifier = Modifier.height(40.dp)) + + Box { + PlatformButton( + modifier = + Modifier + // Remove the built-in padding applied inside PlatformButton: + .padding(vertical = 0.dp) + .width(UserSwitcherDropdownWidth) + .height(UserSwitcherDropdownHeight), + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, + contentColor = MaterialTheme.colorScheme.onSurface, + ), + onClick = { setDropdownExpanded(!isDropdownExpanded) }, + ) { + val context = LocalContext.current + Text( + text = checkNotNull(firstDropdownItem.text.loadText(context)), + style = MaterialTheme.typography.headlineSmall, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + Spacer(modifier = Modifier.weight(1f)) + + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = null, + modifier = Modifier.size(32.dp), + ) + } + + UserSwitcherDropdownMenu( + isExpanded = isDropdownExpanded, + items = dropdownItems, + onDismissed = { setDropdownExpanded(false) }, + ) + } + } + } +} + +/** + * Renders the dropdown menu that displays the actual users and/or user actions that can be + * selected. + */ +@Composable +private fun UserSwitcherDropdownMenu( + isExpanded: Boolean, + items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>, + onDismissed: () -> Unit, +) { + val context = LocalContext.current + + // TODO(b/303071855): once the FR is fixed, remove this composition local override. + MaterialTheme( + colorScheme = + MaterialTheme.colorScheme.copy( + surface = MaterialTheme.colorScheme.surfaceContainerHighest, + ), + shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(28.dp)), + ) { + DropdownMenu( + expanded = isExpanded, + onDismissRequest = onDismissed, + offset = + DpOffset( + x = 0.dp, + y = -UserSwitcherDropdownHeight, + ), + modifier = Modifier.width(UserSwitcherDropdownWidth), + ) { + items.forEach { userSwitcherDropdownItem -> + DropdownMenuItem( + leadingIcon = { + Icon( + icon = userSwitcherDropdownItem.icon, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(28.dp), + ) + }, + text = { + Text( + text = checkNotNull(userSwitcherDropdownItem.text.loadText(context)), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + }, + onClick = { + onDismissed() + userSwitcherDropdownItem.onClick() + }, + ) + } + } + } +} + +/** + * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable + * by double-tapping on the side. + */ +@Composable +private fun SplitLayout( + viewModel: BouncerViewModel, + dialogFactory: BouncerDialogFactory, + modifier: Modifier = Modifier, +) { + SwappableLayout( + startContent = { startContentModifier -> + StandardLayout( + viewModel = viewModel, + dialogFactory = dialogFactory, + outputOnly = true, + modifier = startContentModifier, + ) + }, + endContent = { endContentModifier -> + UserInputArea( + viewModel = viewModel, + visibility = UserInputAreaVisibility.INPUT_ONLY, + modifier = endContentModifier, + ) + }, + modifier = modifier + ) +} + +/** + * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background + * to flip their positions. + */ +@Composable +private fun SwappableLayout( + startContent: @Composable (Modifier) -> Unit, + endContent: @Composable (Modifier) -> Unit, + modifier: Modifier = Modifier, +) { + val layoutDirection = LocalLayoutDirection.current + val isLeftToRight = layoutDirection == LayoutDirection.Ltr + val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) } + + Row( + modifier = + modifier.pointerInput(Unit) { + detectTapGestures( + onDoubleTap = { offset -> + // Depending on where the user double tapped, switch the elements such that + // the endContent is closer to the side that was double tapped. + setSwapped(offset.x < size.width / 2) + } + ) + }, + ) { + val animatedOffset by + animateFloatAsState( + targetValue = + if (!isSwapped) { + // When startContent is first, both elements have their natural placement so + // they are not offset in any way. + 0f + } else if (isLeftToRight) { + // Since startContent is not first, the elements have to be swapped + // horizontally. In the case of LTR locales, this means pushing startContent + // to the right, hence the positive number. + 1f + } else { + // Since startContent is not first, the elements have to be swapped + // horizontally. In the case of RTL locales, this means pushing startContent + // to the left, hence the negative number. + -1f + }, + label = "offset", + ) + + startContent( + Modifier.fillMaxHeight().weight(1f).graphicsLayer { + translationX = size.width * animatedOffset + alpha = animatedAlpha(animatedOffset) + } + ) + + Box( + modifier = + Modifier.fillMaxHeight().weight(1f).graphicsLayer { + // A negative sign is used to make sure this is offset in the direction that's + // opposite of the direction that the user switcher is pushed in. + translationX = -size.width * animatedOffset + alpha = animatedAlpha(animatedOffset) + } + ) { + endContent(Modifier.widthIn(max = 400.dp).align(Alignment.BottomCenter)) + } + } +} + +/** + * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap + * anywhere on the background to flip their positions. + * + * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the + * UI for the bouncer will be shown on its own, taking up one side, with the other side just being + * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard + * rendering of the bouncer will be used instead of the side-by-side layout. + */ +@Composable +private fun SideBySideLayout( + viewModel: BouncerViewModel, + dialogFactory: BouncerDialogFactory, + isUserSwitcherVisible: Boolean, + modifier: Modifier = Modifier, +) { + SwappableLayout( + startContent = { startContentModifier -> + if (isUserSwitcherVisible) { + UserSwitcher( + viewModel = viewModel, + modifier = startContentModifier, + ) + } else { + Box( + modifier = startContentModifier, + ) + } + }, + endContent = { endContentModifier -> + StandardLayout( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = endContentModifier, + ) + }, + modifier = modifier, + ) +} + +/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ +@Composable +private fun StackedLayout( + viewModel: BouncerViewModel, + dialogFactory: BouncerDialogFactory, + isUserSwitcherVisible: Boolean, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + ) { + if (isUserSwitcherVisible) { + UserSwitcher( + viewModel = viewModel, + modifier = Modifier.fillMaxWidth().weight(1f), + ) + } + + StandardLayout( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = Modifier.fillMaxWidth().weight(1f), + ) + } +} + +interface BouncerDialogFactory { + operator fun invoke(): AlertDialog +} + +/** Enumerates all supported user-input area visibilities. */ +private enum class UserInputAreaVisibility { + /** + * Only the area where the user enters the input is shown; the area where the input is reflected + * back to the user is not shown. + */ + INPUT_ONLY, + /** + * Only the area where the input is reflected back to the user is shown; the area where the + * input is entered by the user is not shown. + */ + OUTPUT_ONLY, +} + +/** + * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of + * the two reaches a stopping point but `0` in the middle of the transition. + */ +private fun animatedAlpha( + offset: Float, +): Float { + // Describes a curve that is made of two parabolic U-shaped curves mirrored horizontally around + // the y-axis. The U on the left runs between x = -1 and x = 0 while the U on the right runs + // between x = 0 and x = 1. + // + // The minimum values of the curves are at -0.5 and +0.5. + // + // Both U curves are vertically scaled such that they reach the points (-1, 1) and (1, 1). + // + // Breaking it down, it's y = a×(|x|-m)²+b, where: + // x: the offset + // y: the alpha + // m: x-axis center of the parabolic curves, where the minima are. + // b: y-axis offset to apply to the entire curve so the animation spends more time with alpha = + // 0. + // a: amplitude to scale the parabolic curves to reach y = 1 at x = -1, x = 0, and x = +1. + val m = 0.5f + val b = -0.25 + val a = (1 - b) / m.pow(2) + + return max(0f, (a * (abs(offset) - m).pow(2) + b).toFloat()) +} + +private val SelectedUserImageSize = 190.dp +private val UserSwitcherDropdownWidth = SelectedUserImageSize + 2 * 29.dp +private val UserSwitcherDropdownHeight = 60.dp + +private object SceneKeys { + val ContiguousSceneKey = SceneKey("default") + val SplitSceneKey = SceneKey("split") +} + +private object SceneElements { + val AboveFold = ElementKey("above_fold") + val BelowFold = ElementKey("below_fold") +} + +private val SceneTransitions = transitions { + from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() } +} 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 57af2ba59566..d638ffe11abe 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 @@ -16,91 +16,22 @@ package com.android.systemui.bouncer.ui.composable -import android.app.AlertDialog -import android.app.Dialog -import android.content.DialogInterface -import androidx.compose.animation.Crossfade -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment 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.LocalContext -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.times -import com.android.compose.PlatformButton import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneKey as SceneTransitionLayoutSceneKey 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.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 -import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel -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.helper.FoldPosture -import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene import javax.inject.Inject -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.pow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -118,7 +49,7 @@ class BouncerScene @Inject constructor( private val viewModel: BouncerViewModel, - private val dialogFactory: BouncerSceneDialogFactory, + private val dialogFactory: BouncerDialogFactory, ) : ComposableScene { override val key = SceneKey.Bouncer @@ -140,648 +71,22 @@ constructor( @Composable private fun SceneScope.BouncerScene( viewModel: BouncerViewModel, - dialogFactory: BouncerSceneDialogFactory, + dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, ) { val backgroundColor = MaterialTheme.colorScheme.surface - val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() - val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) Box(modifier) { Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) { drawRect(color = backgroundColor) } - val childModifier = Modifier.element(Bouncer.Elements.Content).fillMaxSize() - val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible - - when (layout) { - BouncerSceneLayout.STANDARD -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - modifier = childModifier, - ) - BouncerSceneLayout.SIDE_BY_SIDE -> - SideBySideLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, - modifier = childModifier, - ) - BouncerSceneLayout.STACKED -> - StackedLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, - modifier = childModifier, - ) - BouncerSceneLayout.SPLIT -> - SplitLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - modifier = childModifier, - ) - } - } -} - -/** - * Renders the contents of the actual bouncer UI, the area that takes user input to do an - * authentication attempt, including all messaging UI (directives, reasoning, errors, etc.). - */ -@Composable -private fun StandardLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerSceneDialogFactory, - modifier: Modifier = Modifier, - outputOnly: Boolean = false, -) { - val foldPosture: FoldPosture by foldPosture() - val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState() - val isSplitAroundTheFold = - foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired - val currentSceneKey = - if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey - - SceneTransitionLayout( - currentScene = currentSceneKey, - onChangeScene = {}, - transitions = SceneTransitions, - modifier = modifier, - ) { - scene(SceneKeys.ContiguousSceneKey) { - FoldSplittable( - viewModel = viewModel, - dialogFactory = dialogFactory, - outputOnly = outputOnly, - isSplit = false, - ) - } - - scene(SceneKeys.SplitSceneKey) { - FoldSplittable( - viewModel = viewModel, - dialogFactory = dialogFactory, - outputOnly = outputOnly, - isSplit = true, - ) - } - } -} - -/** - * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user - * switcher UI) and laid out vertically, centered horizontally. - * - * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't - * render across the location of the fold hardware when the device is fully or part-way unfolded - * with the fold hinge in a horizontal position. - * - * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN - * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter - * their PIN or pattern. - */ -@Composable -private fun SceneScope.FoldSplittable( - viewModel: BouncerViewModel, - dialogFactory: BouncerSceneDialogFactory, - outputOnly: Boolean, - isSplit: Boolean, - modifier: Modifier = Modifier, -) { - val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() - val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() - var dialog: Dialog? by remember { mutableStateOf(null) } - val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() - val splitRatio = - LocalContext.current.resources.getFloat( - R.dimen.motion_layout_half_fold_bouncer_height_ratio - ) - - Column(modifier = modifier.padding(horizontal = 32.dp)) { - // Content above the fold, when split on a foldable device in a "table top" posture: - Box( - modifier = - Modifier.element(SceneElements.AboveFold).fillMaxWidth().thenIf(isSplit) { - Modifier.weight(splitRatio) - }, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth().padding(top = 92.dp), - ) { - Crossfade( - targetState = message, - label = "Bouncer message", - animationSpec = if (message.isUpdateAnimated) tween() else snap(), - ) { message -> - Text( - text = message.text, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, - ) - } - - Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) - - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.OUTPUT_ONLY, - ) - } - } - - // Content below the fold, when split on a foldable device in a "table top" posture: - Box( - modifier = - Modifier.element(SceneElements.BelowFold).fillMaxWidth().thenIf(isSplit) { - Modifier.weight(1 - splitRatio) - }, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth(), - ) { - if (!outputOnly) { - Box(Modifier.weight(1f)) { - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.INPUT_ONLY, - modifier = Modifier.align(Alignment.Center), - ) - } - } - - Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) - - val actionButtonModifier = Modifier.height(56.dp) - - actionButton.let { actionButtonViewModel -> - if (actionButtonViewModel != null) { - BouncerActionButton( - viewModel = actionButtonViewModel, - modifier = actionButtonModifier, - ) - } else { - Spacer(modifier = actionButtonModifier) - } - } - - Spacer(Modifier.height(48.dp)) - } - } - - if (dialogMessage != null) { - if (dialog == null) { - dialog = - dialogFactory().apply { - setMessage(dialogMessage) - setButton( - DialogInterface.BUTTON_NEUTRAL, - context.getString(R.string.ok), - ) { _, _ -> - viewModel.onThrottlingDialogDismissed() - } - setCancelable(false) - setCanceledOnTouchOutside(false) - show() - } - } - } else { - dialog?.dismiss() - dialog = null - } - } -} - -/** - * Renders the user input area, where the user interacts with the UI to enter their credentials. - * - * For example, this can be the pattern input area, the password text box, or pin pad. - */ -@Composable -private fun UserInputArea( - viewModel: BouncerViewModel, - visibility: UserInputAreaVisibility, - modifier: Modifier = Modifier, -) { - val authMethodViewModel: AuthMethodBouncerViewModel? by - viewModel.authMethodViewModel.collectAsState() - - when (val nonNullViewModel = authMethodViewModel) { - is PinBouncerViewModel -> - when (visibility) { - UserInputAreaVisibility.OUTPUT_ONLY -> - PinInputDisplay( - viewModel = nonNullViewModel, - modifier = modifier, - ) - UserInputAreaVisibility.INPUT_ONLY -> - PinPad( - viewModel = nonNullViewModel, - modifier = modifier, - ) - } - is PasswordBouncerViewModel -> - if (visibility == UserInputAreaVisibility.INPUT_ONLY) { - PasswordBouncer( - viewModel = nonNullViewModel, - modifier = modifier, - ) - } - is PatternBouncerViewModel -> - if (visibility == UserInputAreaVisibility.INPUT_ONLY) { - PatternBouncer( - viewModel = nonNullViewModel, - modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false) - ) - } - else -> Unit - } -} - -/** - * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call. - */ -@OptIn(ExperimentalFoundationApi::class) -@Composable -private fun BouncerActionButton( - viewModel: BouncerActionButtonModel, - modifier: Modifier = Modifier, -) { - Button( - onClick = viewModel.onClick, - modifier = - modifier.thenIf(viewModel.onLongClick != null) { - Modifier.combinedClickable( - onClick = viewModel.onClick, - onLongClick = viewModel.onLongClick, - ) - }, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer, - ), - ) { - Text( - text = viewModel.label, - style = MaterialTheme.typography.bodyMedium, + // Separate the bouncer content into a reusable composable that doesn't have any SceneScope + // dependencies + BouncerContent( + viewModel, + dialogFactory, + Modifier.element(Bouncer.Elements.Content).fillMaxSize() ) } } - -/** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */ -@Composable -private fun UserSwitcher( - viewModel: BouncerViewModel, - modifier: Modifier = Modifier, -) { - val selectedUserImage by viewModel.selectedUserImage.collectAsState(null) - val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList()) - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = modifier, - ) { - selectedUserImage?.let { - Image( - bitmap = it.asImageBitmap(), - contentDescription = null, - modifier = Modifier.size(SelectedUserImageSize), - ) - } - - val (isDropdownExpanded, setDropdownExpanded) = remember { mutableStateOf(false) } - - dropdownItems.firstOrNull()?.let { firstDropdownItem -> - Spacer(modifier = Modifier.height(40.dp)) - - Box { - PlatformButton( - modifier = - Modifier - // Remove the built-in padding applied inside PlatformButton: - .padding(vertical = 0.dp) - .width(UserSwitcherDropdownWidth) - .height(UserSwitcherDropdownHeight), - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, - contentColor = MaterialTheme.colorScheme.onSurface, - ), - onClick = { setDropdownExpanded(!isDropdownExpanded) }, - ) { - val context = LocalContext.current - Text( - text = checkNotNull(firstDropdownItem.text.loadText(context)), - style = MaterialTheme.typography.headlineSmall, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - - Spacer(modifier = Modifier.weight(1f)) - - Icon( - imageVector = Icons.Default.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(32.dp), - ) - } - - UserSwitcherDropdownMenu( - isExpanded = isDropdownExpanded, - items = dropdownItems, - onDismissed = { setDropdownExpanded(false) }, - ) - } - } - } -} - -/** - * Renders the dropdowm menu that displays the actual users and/or user actions that can be - * selected. - */ -@Composable -private fun UserSwitcherDropdownMenu( - isExpanded: Boolean, - items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>, - onDismissed: () -> Unit, -) { - val context = LocalContext.current - - // TODO(b/303071855): once the FR is fixed, remove this composition local override. - MaterialTheme( - colorScheme = - MaterialTheme.colorScheme.copy( - surface = MaterialTheme.colorScheme.surfaceContainerHighest, - ), - shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(28.dp)), - ) { - DropdownMenu( - expanded = isExpanded, - onDismissRequest = onDismissed, - offset = - DpOffset( - x = 0.dp, - y = -UserSwitcherDropdownHeight, - ), - modifier = Modifier.width(UserSwitcherDropdownWidth), - ) { - items.forEach { userSwitcherDropdownItem -> - DropdownMenuItem( - leadingIcon = { - Icon( - icon = userSwitcherDropdownItem.icon, - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(28.dp), - ) - }, - text = { - Text( - text = checkNotNull(userSwitcherDropdownItem.text.loadText(context)), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - ) - }, - onClick = { - onDismissed() - userSwitcherDropdownItem.onClick() - }, - ) - } - } - } -} - -/** - * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable - * by double-tapping on the side. - */ -@Composable -private fun SplitLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerSceneDialogFactory, - modifier: Modifier = Modifier, -) { - SwappableLayout( - startContent = { startContentModifier -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - outputOnly = true, - modifier = startContentModifier, - ) - }, - endContent = { endContentModifier -> - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.INPUT_ONLY, - modifier = endContentModifier, - ) - }, - modifier = modifier - ) -} - -/** - * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background - * to flip their positions. - */ -@Composable -private fun SwappableLayout( - startContent: @Composable (Modifier) -> Unit, - endContent: @Composable (Modifier) -> Unit, - modifier: Modifier = Modifier, -) { - val layoutDirection = LocalLayoutDirection.current - val isLeftToRight = layoutDirection == LayoutDirection.Ltr - val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) } - - Row( - modifier = - modifier.pointerInput(Unit) { - detectTapGestures( - onDoubleTap = { offset -> - // Depending on where the user double tapped, switch the elements such that - // the endContent is closer to the side that was double tapped. - setSwapped(offset.x < size.width / 2) - } - ) - }, - ) { - val animatedOffset by - animateFloatAsState( - targetValue = - if (!isSwapped) { - // When startContent is first, both elements have their natural placement so - // they are not offset in any way. - 0f - } else if (isLeftToRight) { - // Since startContent is not first, the elements have to be swapped - // horizontally. In the case of LTR locales, this means pushing startContent - // to the right, hence the positive number. - 1f - } else { - // Since startContent is not first, the elements have to be swapped - // horizontally. In the case of RTL locales, this means pushing startContent - // to the left, hence the negative number. - -1f - }, - label = "offset", - ) - - startContent( - Modifier.fillMaxHeight().weight(1f).graphicsLayer { - translationX = size.width * animatedOffset - alpha = animatedAlpha(animatedOffset) - } - ) - - Box( - modifier = - Modifier.fillMaxHeight().weight(1f).graphicsLayer { - // A negative sign is used to make sure this is offset in the direction that's - // opposite of the direction that the user switcher is pushed in. - translationX = -size.width * animatedOffset - alpha = animatedAlpha(animatedOffset) - } - ) { - endContent(Modifier.widthIn(max = 400.dp).align(Alignment.BottomCenter)) - } - } -} - -/** - * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap - * anywhere on the background to flip their positions. - * - * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the - * UI for the bouncer will be shown on its own, taking up one side, with the other side just being - * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard - * rendering of the bouncer will be used instead of the side-by-side layout. - */ -@Composable -private fun SideBySideLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerSceneDialogFactory, - isUserSwitcherVisible: Boolean, - modifier: Modifier = Modifier, -) { - SwappableLayout( - startContent = { startContentModifier -> - if (isUserSwitcherVisible) { - UserSwitcher( - viewModel = viewModel, - modifier = startContentModifier, - ) - } else { - Box( - modifier = startContentModifier, - ) - } - }, - endContent = { endContentModifier -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - modifier = endContentModifier, - ) - }, - modifier = modifier, - ) -} - -/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ -@Composable -private fun StackedLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerSceneDialogFactory, - isUserSwitcherVisible: Boolean, - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier, - ) { - if (isUserSwitcherVisible) { - UserSwitcher( - viewModel = viewModel, - modifier = Modifier.fillMaxWidth().weight(1f), - ) - } - - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - modifier = Modifier.fillMaxWidth().weight(1f), - ) - } -} - -interface BouncerSceneDialogFactory { - operator fun invoke(): AlertDialog -} - -/** Enumerates all supported user-input area visibilities. */ -private enum class UserInputAreaVisibility { - /** - * Only the area where the user enters the input is shown; the area where the input is reflected - * back to the user is not shown. - */ - INPUT_ONLY, - /** - * Only the area where the input is reflected back to the user is shown; the area where the - * input is entered by the user is not shown. - */ - OUTPUT_ONLY, -} - -/** - * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of - * the two reaches a stopping point but `0` in the middle of the transition. - */ -private fun animatedAlpha( - offset: Float, -): Float { - // Describes a curve that is made of two parabolic U-shaped curves mirrored horizontally around - // the y-axis. The U on the left runs between x = -1 and x = 0 while the U on the right runs - // between x = 0 and x = 1. - // - // The minimum values of the curves are at -0.5 and +0.5. - // - // Both U curves are vertically scaled such that they reach the points (-1, 1) and (1, 1). - // - // Breaking it down, it's y = a×(|x|-m)²+b, where: - // x: the offset - // y: the alpha - // m: x-axis center of the parabolic curves, where the minima are. - // b: y-axis offset to apply to the entire curve so the animation spends more time with alpha = - // 0. - // a: amplitude to scale the parabolic curves to reach y = 1 at x = -1, x = 0, and x = +1. - val m = 0.5f - val b = -0.25 - val a = (1 - b) / m.pow(2) - - return max(0f, (a * (abs(offset) - m).pow(2) + b).toFloat()) -} - -private val SelectedUserImageSize = 190.dp -private val UserSwitcherDropdownWidth = SelectedUserImageSize + 2 * 29.dp -private val UserSwitcherDropdownHeight = 60.dp - -private object SceneKeys { - val ContiguousSceneKey = SceneTransitionLayoutSceneKey("default") - val SplitSceneKey = SceneTransitionLayoutSceneKey("split") -} - -private object SceneElements { - val AboveFold = ElementKey("above_fold") - val BelowFold = ElementKey("below_fold") -} - -private val SceneTransitions = transitions { - from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() } -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index cc95a4b72731..30536547ae5c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -144,8 +144,7 @@ private fun SceneScope.LockscreenScene( modifier = Modifier.fillMaxSize(), ) - val notificationStackPosition by - viewModel.keyguardRoot.notificationPositionOnLockscreen.collectAsState() + val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState() Layout( modifier = Modifier.fillMaxSize(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index c9d31fdcb8e5..c49c19785624 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -72,8 +72,8 @@ object Notifications { @Composable fun SceneScope.HeadsUpNotificationSpace( viewModel: NotificationsPlaceholderViewModel, - isPeekFromBottom: Boolean = false, modifier: Modifier = Modifier, + isPeekFromBottom: Boolean = false, ) { NotificationPlaceholder( viewModel = viewModel, @@ -149,11 +149,11 @@ private fun SceneScope.NotificationPlaceholder( form: Form, modifier: Modifier = Modifier, ) { - val key = Notifications.Elements.NotificationPlaceholder + val elementKey = Notifications.Elements.NotificationPlaceholder Box( modifier = modifier - .element(key) + .element(elementKey) .debugBackground(viewModel) .onSizeChanged { size: IntSize -> debugLog(viewModel) { "STACK onSizeChanged: size=$size" } @@ -166,7 +166,7 @@ private fun SceneScope.NotificationPlaceholder( " bounds=${coordinates.boundsInWindow()}" } val boundsInWindow = coordinates.boundsInWindow() - viewModel.setPlaceholderPositionInWindow( + viewModel.onBoundsChanged( top = boundsInWindow.top, bottom = boundsInWindow.bottom, ) @@ -176,7 +176,7 @@ private fun SceneScope.NotificationPlaceholder( animateSharedFloatAsState( value = if (form == Form.HunFromTop) 0f else 1f, key = SharedExpansionValue, - element = key + element = elementKey ) debugLog(viewModel) { "STACK composed: expansion=$animatedExpansion" } if (viewModel.isPlaceholderTextVisible) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt new file mode 100644 index 000000000000..820c0564c1a7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.domain + +import android.graphics.drawable.Drawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.qs.tiles.impl.location.qsLocationTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth +import junit.framework.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class LocationTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val qsTileConfig = kosmos.qsLocationTileConfig + private val mapper by lazy { LocationTileMapper(context) } + + @Test + fun mapsDisabledDataToInactiveState() { + val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false)) + + val actualActivationState = tileState.activationState + Assert.assertEquals(QSTileState.ActivationState.INACTIVE, actualActivationState) + } + + @Test + fun mapsEnabledDataToActiveState() { + val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true)) + + val actualActivationState = tileState.activationState + Assert.assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState) + } + + @Test + fun mapsEnabledDataToOnIconState() { + val fakeDrawable = mock<Drawable>() + context.orCreateTestableResources.addOverride(R.drawable.qs_location_icon_on, fakeDrawable) + val expectedIcon = Icon.Loaded(fakeDrawable, null) + + val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true)) + + val actualIcon = tileState.icon() + Truth.assertThat(actualIcon).isEqualTo(expectedIcon) + } + + @Test + fun mapsDisabledDataToOffIconState() { + val fakeDrawable = mock<Drawable>() + context.orCreateTestableResources.addOverride(R.drawable.qs_location_icon_off, fakeDrawable) + val expectedIcon = Icon.Loaded(fakeDrawable, null) + + val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false)) + + val actualIcon = tileState.icon() + Truth.assertThat(actualIcon).isEqualTo(expectedIcon) + } + + @Test + fun supportsClickAndLongClickActions() { + val dontCare = true + + val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(dontCare)) + + val supportedActions = tileState.supportedActions + Truth.assertThat(supportedActions) + .containsExactly(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt new file mode 100644 index 000000000000..8fdc93be4ba2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.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.qs.tiles.impl.location.interactor + +import android.os.UserHandle +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.utils.leaks.FakeLocationController +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +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 LocationTileDataInteractorTest : SysuiTestCase() { + private lateinit var controller: FakeLocationController + private lateinit var underTest: LocationTileDataInteractor + + @Before + fun setup() { + controller = FakeLocationController(LeakCheck()) + underTest = LocationTileDataInteractor(controller) + } + + @Test + fun isAvailableRegardlessOfController() = runTest { + controller.setLocationEnabled(false) + + runCurrent() + val availability by collectLastValue(underTest.availability(TEST_USER)) + + Truth.assertThat(availability).isTrue() + } + + @Test + fun dataMatchesController() = runTest { + controller.setLocationEnabled(false) + val flowValues: List<LocationTileModel> by + collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))) + + runCurrent() + controller.setLocationEnabled(true) + runCurrent() + controller.setLocationEnabled(false) + runCurrent() + + Truth.assertThat(flowValues.size).isEqualTo(3) + Truth.assertThat(flowValues.map { it.isEnabled }) + .containsExactly(false, true, false) + .inOrder() + } + + private companion object { + val TEST_USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..0fb8ae697190 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.interactor + +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.intentInputs +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick +import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.statusbar.phone.FakeKeyguardStateController +import com.android.systemui.statusbar.policy.LocationController +import com.google.common.truth.Truth.assertThat +import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class LocationTileUserActionInteractorTest : SysuiTestCase() { + + private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler() + private val keyguardController = FakeKeyguardStateController() + + private lateinit var underTest: LocationTileUserActionInteractor + + @Mock private lateinit var locationController: LocationController + @Mock private lateinit var activityStarter: ActivityStarter + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + val kosmos = Kosmos() + underTest = + LocationTileUserActionInteractor( + EmptyCoroutineContext, + kosmos.testScope, + locationController, + qsTileIntentUserActionHandler, + activityStarter, + keyguardController, + ) + } + + @Test + fun handleClickToEnable() = runTest { + val stateBeforeClick = false + + underTest.handleInput(click(LocationTileModel(stateBeforeClick))) + + Mockito.verify(locationController).setLocationEnabled(!stateBeforeClick) + } + + @Test + fun handleClickToDisable() = runTest { + val stateBeforeClick = true + + underTest.handleInput(click(LocationTileModel(stateBeforeClick))) + + Mockito.verify(locationController).setLocationEnabled(!stateBeforeClick) + } + + @Test + fun handleLongClick() = runTest { + val dontCare = true + + underTest.handleInput(longClick(LocationTileModel(dontCare))) + + assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1) + val intentInput = qsTileIntentUserActionHandler.intentInputs.last() + val actualIntentAction = intentInput.intent.action + val expectedIntentAction = Settings.ACTION_LOCATION_SOURCE_SETTINGS + assertThat(actualIntentAction).isEqualTo(expectedIntentAction) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt new file mode 100644 index 000000000000..f04dfd1b8fdb --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.NotificationContainerBounds +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.Flags +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags +import com.android.systemui.scene.shared.flag.sceneContainerFlags +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationStackAppearanceViewModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { + + private val kosmos = + testKosmos().apply { + sceneContainerFlags = FakeSceneContainerFlags(enabled = true) + featureFlagsClassic.apply { + set(Flags.FULL_SCREEN_USER_SWITCHER, false) + set(Flags.NSSL_DEBUG_LINES, false) + } + } + private val testScope = kosmos.testScope + private val placeholderViewModel = kosmos.notificationsPlaceholderViewModel + private val appearanceViewModel = kosmos.notificationStackAppearanceViewModel + private val sceneInteractor = kosmos.sceneInteractor + + @Test + fun updateBounds() = + testScope.runTest { + val bounds by collectLastValue(appearanceViewModel.stackBounds) + + val top = 200f + val bottom = 550f + placeholderViewModel.onBoundsChanged(top, bottom) + assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom)) + } + + @Test + fun updateShadeExpansion() = + testScope.runTest { + val expandFraction by collectLastValue(appearanceViewModel.expandFraction) + assertThat(expandFraction).isEqualTo(0f) + + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(scene = SceneKey.Lockscreen) + ) + sceneInteractor.setTransitionState(transitionState) + sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason") + val transitionProgress = MutableStateFlow(0f) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = transitionProgress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + val steps = 10 + repeat(steps) { repetition -> + val progress = (1f / steps) * (repetition + 1) + transitionProgress.value = progress + runCurrent() + assertThat(expandFraction).isWithin(0.01f).of(progress) + } + + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + assertThat(expandFraction).isWithin(0.01f).of(1f) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt new file mode 100644 index 000000000000..c7411cd78b78 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.NotificationContainerBounds +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationStackAppearanceInteractorTest : SysuiTestCase() { + + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val underTest = kosmos.notificationStackAppearanceInteractor + + @Test + fun stackBounds() = + testScope.runTest { + val stackBounds by collectLastValue(underTest.stackBounds) + + val bounds1 = + NotificationContainerBounds( + top = 100f, + bottom = 200f, + isAnimated = true, + ) + underTest.setStackBounds(bounds1) + assertThat(stackBounds).isEqualTo(bounds1) + + val bounds2 = + NotificationContainerBounds( + top = 200f, + bottom = 300f, + isAnimated = false, + ) + underTest.setStackBounds(bounds2) + assertThat(stackBounds).isEqualTo(bounds2) + } + + @Test(expected = IllegalStateException::class) + fun setStackBounds_withImproperBounds_throwsException() = + testScope.runTest { + underTest.setStackBounds( + NotificationContainerBounds( + top = 100f, + bottom = 99f, + ) + ) + } +} diff --git a/packages/SystemUI/res/anim/instant_fade_out.xml b/packages/SystemUI/res/anim/instant_fade_out.xml new file mode 100644 index 000000000000..800420b4ff60 --- /dev/null +++ b/packages/SystemUI/res/anim/instant_fade_out.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:fromAlpha="1.0" + android:toAlpha="0.0" + android:interpolator="@android:interpolator/linear_out_slow_in" + android:duration="0"/> + diff --git a/packages/SystemUI/res/drawable/ksh_key_item_background.xml b/packages/SystemUI/res/drawable/ksh_key_item_background.xml index 75ff30d25aa7..1db48fad2e3f 100644 --- a/packages/SystemUI/res/drawable/ksh_key_item_background.xml +++ b/packages/SystemUI/res/drawable/ksh_key_item_background.xml @@ -17,5 +17,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@color/ksh_key_item_background" /> - <corners android:radius="2dp" /> + <corners android:radius="8dp" /> </shape> diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml index 9c08f5ef4cfe..355e75d0716b 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 android:id="@+id/notification_background_color_layer"> + <item> <shape> <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" /> </shape> diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml index fcf9638440c7..a0051008ddd6 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml @@ -22,8 +22,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="48dp" - android:paddingStart="24dp" - android:paddingEnd="24dp" android:paddingBottom="8dp"> <ImageView android:id="@+id/keyboard_shortcuts_icon" @@ -57,5 +55,6 @@ android:layout_alignParentEnd="true" android:textSize="14sp" android:scrollHorizontally="false" - android:layout_centerVertical="true"/> + android:layout_centerVertical="true" + android:padding="0dp" /> </com.android.systemui.statusbar.KeyboardShortcutAppItemLayout> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml index 0759990f1677..4f100f6b94d1 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml @@ -21,7 +21,5 @@ android:textSize="14sp" android:fontFamily="sans-serif-medium" android:importantForAccessibility="yes" - android:paddingStart="24dp" android:paddingTop="20dp" - android:paddingEnd="24dp" android:paddingBottom="10dp"/> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml index a3901d084930..f96edbf0056e 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_icon_view.xml @@ -18,7 +18,12 @@ <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:padding="@dimen/ksh_item_padding" + android:minWidth="48dp" + android:minHeight="32dp" + android:paddingLeft="@dimen/ksh_key_view_padding_horizontal" + android:paddingRight="@dimen/ksh_key_view_padding_horizontal" + android:paddingTop="@dimen/ksh_key_view_padding_vertical" + android:paddingBottom="@dimen/ksh_key_view_padding_vertical" android:layout_marginStart="@dimen/ksh_item_margin_start" - android:scaleType="fitXY" + android:scaleType="matrix" android:background="@drawable/ksh_key_item_background" /> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_plus_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_plus_view.xml deleted file mode 100644 index 727f2c15d5e5..000000000000 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_plus_view.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:padding="@dimen/ksh_item_padding" - android:layout_marginLeft="0dp" - android:layout_marginRight="0dp" - android:text="+" - style="@style/ShortcutItemBackground" - android:textColor="?android:attr/textColorPrimary" - android:singleLine="true" - android:gravity="center" - android:textSize="@dimen/ksh_item_text_size" - android:textAllCaps="true"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_vertical_bar_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_separator_view.xml index 00ef94784740..8772a732e829 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_vertical_bar_view.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_separator_view.xml @@ -14,15 +14,15 @@ limitations under the License. --> <TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:padding="@dimen/ksh_item_padding" - android:layout_marginLeft="0dp" - android:layout_marginRight="0dp" - android:text="|" - style="@style/ShortcutItemBackground" - android:textColor="?android:attr/textColorPrimary" - android:singleLine="true" - android:gravity="center" - android:textSize="@dimen/ksh_item_text_size" - android:textAllCaps="true"/>
\ No newline at end of file + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minHeight="32dp" + android:padding="@dimen/ksh_item_padding" + android:layout_marginLeft="0dp" + android:layout_marginRight="0dp" + android:text="@string/keyboard_shortcut_join" + android:textColor="?androidprv:attr/materialColorOnSurfaceVariant" + android:singleLine="true" + android:gravity="center" + android:textSize="@dimen/ksh_item_text_size" /> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml index b06f7fc3c69d..42bbf25d6c26 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_key_view.xml @@ -14,14 +14,18 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:padding="@dimen/ksh_item_padding" - android:layout_marginStart="@dimen/ksh_item_margin_start" - android:background="@drawable/ksh_key_item_background" - android:textColor="@color/ksh_key_item_color" - android:singleLine="true" - android:gravity="center" - android:textSize="@dimen/ksh_item_text_size" - android:textAllCaps="true"/> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="32dp" + android:minHeight="32dp" + android:paddingLeft="@dimen/ksh_key_view_padding_horizontal" + android:paddingRight="@dimen/ksh_key_view_padding_horizontal" + android:paddingTop="@dimen/ksh_key_view_padding_vertical" + android:paddingBottom="@dimen/ksh_key_view_padding_vertical" + android:layout_marginStart="@dimen/ksh_item_margin_start" + android:background="@drawable/ksh_key_item_background" + android:textColor="?androidprv:attr/materialColorOnSurfaceVariant" + android:singleLine="true" + android:gravity="center" + android:textSize="@dimen/ksh_item_text_size" /> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml index dbcd2636134f..61f69c04c174 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml @@ -70,17 +70,14 @@ <HorizontalScrollView android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginStart="49dp" + android:layout_marginEnd="0dp" android:scrollbars="none"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal"> - <View - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_marginStart="37dp"/> - <Button android:id="@+id/shortcut_system" android:layout_width="wrap_content" @@ -116,6 +113,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="50dp" + android:layout_marginStart="49dp" + android:layout_marginEnd="49dp" android:layout_gravity="center_horizontal" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="?android:attr/textColorPrimary" @@ -126,8 +125,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" - android:layout_marginStart="25dp" - android:layout_marginEnd="25dp"> + android:layout_marginStart="49dp" + android:layout_marginEnd="49dp" + android:overScrollMode="never" + android:layout_marginBottom="16dp"> <LinearLayout android:id="@+id/keyboard_shortcuts_container" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml index 7aba1cf6e0dc..9e54ab1dc361 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml @@ -22,6 +22,8 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginStart="24dp" + android:layout_marginEnd="24dp" android:orientation="vertical"> <ScrollView android:id="@+id/keyboard_shortcuts_scroll_view" diff --git a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml index 130472d67500..02c8c3a5202e 100644 --- a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml +++ b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml @@ -17,7 +17,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="250dp" - android:layout_height="48dp" + android:layout_height="wrap_content" + android:minHeight="48dp" android:orientation="vertical" android:padding="12dp"> <TextView diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml index d9f4b7961636..8916e427a1d5 100644 --- a/packages/SystemUI/res/layout/screen_record_options.xml +++ b/packages/SystemUI/res/layout/screen_record_options.xml @@ -47,6 +47,7 @@ android:layout_weight="0" android:layout_gravity="end" android:id="@+id/screenrecord_audio_switch" + android:contentDescription="@string/screenrecord_audio_label" style="@style/ScreenRecord.Switch" android:importantForAccessibility="yes"/> </LinearLayout> @@ -79,6 +80,7 @@ android:minWidth="48dp" android:layout_height="48dp" android:id="@+id/screenrecord_taps_switch" + android:contentDescription="@string/screenrecord_taps_label" style="@style/ScreenRecord.Switch" android:importantForAccessibility="yes"/> </LinearLayout> diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index b0c4fa56a73a..45a2e8a5c800 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth gekoppel."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth-toestelikoon"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klik om toestelbesonderhede op te stel."</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klik om alle toestelle te sien"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klik om met nuwe toestel te koppel"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batterypersentasie is onbekend."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Gekoppel aan <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Gekoppel aan <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gebruik Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Gekoppel"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gestoor"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ontkoppel"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveer"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batterykrag"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Oudio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kopstuk"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans stadig • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swiep links om die gemeenskaplike tutoriaal te begin"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Maak die legstukredigeerder oop"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Verwyder ’n legstuk"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Voeg legstuk by"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Wissel gebruiker"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aftrekkieslys"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Onlangs gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Word gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Onlangs gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Sleutelbordlig"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Vlak %1$d van %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index b17455241d57..bac72bf8cd9f 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ብሉቱዝ ተያይዟል።"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"የብሉቱዝ መሣሪያ አዶ"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"የመሣሪያ ዝርዝርን ለማዋቀር ጠቅ ያድርጉ"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"ሁሉንም መሣሪያዎች ለማየት ጠቅ ያድርጉ"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"ከአዲስ መሣሪያ ጋር ለማጣመር ጠቅ ያድርጉ"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"የባትሪ መቶኛ አይታወቅም።"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ከ<xliff:g id="BLUETOOTH">%s</xliff:g> ጋር ተገናኝቷል።"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"ከ<xliff:g id="CAST">%s</xliff:g> ጋር ተገናኝቷል።"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ብሉቱዝን ይጠቀሙ"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ተገናኝቷል"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ተቀምጧል"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ግንኙነትን አቋርጥ"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ያግብሩ"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ባትሪ"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ኦዲዮ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ማዳመጫ"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • በዝግታ ኃይልን በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ኃይል በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"የጋራ አጋዥ ሥልጠናውን ለመጀመር ወደ ግራ ያንሸራትቱ።"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"ምግብር መራጩን ክፈት"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"የምግብር አርታዒውን ይክፈቱ"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"ምግብርን አስወግድ"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ምግብር አክል"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ተጠቃሚ ቀይር"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ወደታች ተጎታች ምናሌ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 0b53b41ffa7d..c188535f224a 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"تم توصيل البلوتوث."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"رمز الجهاز الذي يتضمّن بلوتوث"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"انقر هنا لضبط إعدادات الجهاز."</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"انقر لعرض جميع الأجهزة."</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"انقر لإقران جهاز جديد."</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"نسبة شحن البطارية غير معروفة."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"متصل بـ <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"تم الاتصال بـ <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"استخدام البلوتوث"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"متّصل"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"محفوظ"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"إلغاء الربط"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"تفعيل"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"مستوى طاقة البطارية <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"سماعة الرأس"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن ببطء • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"مرِّر سريعًا لليمين لبدء الدليل التوجيهي العام."</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"فتح محرِّر التطبيقات المصغّرة"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"إزالة تطبيق مصغّر"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"إضافة تطبيق مصغّر"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تبديل المستخدم"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"القائمة المنسدلة"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"سيتم حذف كل التطبيقات والبيانات في هذه الجلسة."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"تم الاستخدام مؤخرًا في <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"قيد الاستخدام في <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"تم الاستخدام مؤخرًا في <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"الإضاءة الخلفية للوحة المفاتيح"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"مستوى الإضاءة: %1$d من %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index e593a5bb731b..59eca3a28b75 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযোগ হ’ল।"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ব্লুটুথ ডিভাইচৰ চিহ্ন"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ডিভাইচৰ সবিশেষ কনফিগাৰ কৰিবলৈ ক্লিক কৰক"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"আটাইবোৰ ডিভাইচ চাবলৈ ক্লিক কৰক"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"নতুন ডিভাইচ পেয়াৰ কৰিবলৈ ক্লিক কৰক"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"বেটাৰীৰ চাৰ্জৰ শতাংশ অজ্ঞাত।"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>ৰ লগত সংযোগ কৰা হ’ল।"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>ত সংযোগ হ’ল।"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ব্লুটুথ ব্যৱহাৰ কৰক"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"সংযুক্ত আছে"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ছেভ কৰা হৈছে"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"সংযোগ বিচ্ছিন্ন কৰক"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"সক্ৰিয় কৰক"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"বেটাৰী <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিঅ’"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডছেট"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • লাহে লাহে চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"সম্প্ৰদায় সম্পৰ্কীয় নিৰ্দেশনা আৰম্ভ কৰিবলৈ বাওঁফালে ছোৱাইপ কৰক"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"ৱিজেট বাছনিকৰ্তাটো খোলক"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ৱিজেট সম্পাদকটো খোলক"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"এটা ৱিজেট আঁতৰাওক"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ৱিজেট যোগ দিয়ক"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যৱহাৰকাৰী সলনি কৰক"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুল-ডাউনৰ মেনু"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 4a2a595c8893..121e276af72a 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth qoşulub."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth cihazı ikonası"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Cihaz təfərrüatlarını konfiqurasiya etmək üçün klikləyin"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Bütün cihazları görmək üçün klikləyin"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Yeni cihazı birləşdirmək üçün klikləyin"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batareyanın faizi naməlumdur."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> üzərindən qoşuldu."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> cihazına qoşulub."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth aç"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Qoşulub"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Yadda saxlandı"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"əlaqəni kəsin"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivləşdirin"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batareya"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Qulaqlıq"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Asta şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"İcma təlimatını başlatmaq üçün sola sürüşdürün"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidcet redaktorunu açın"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Vidceti silin"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Vidcet əlavə edin"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aşağı çəkilən menyu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Bu yaxınlarda <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) istifadə edib"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) istifadə edir"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Bu yaxınlarda <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) istifadə edib"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Klaviatura işığı"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Səviyyə %1$d/%2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index c7f2eafb9a4f..4c535d81aede 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je priključen."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona Bluetooth uređaja"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknite da biste konfigurisali detalje o uređaju"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknite da biste videli sve uređaje"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknite da biste uparili nov uređaj"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procenat napunjenosti baterije nije poznat."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezani ste sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Povezani smo sa uređajem <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinite vezu"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivirajte"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivo baterije je <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Puni se • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prevucite ulevo da biste započeli zajednički vodič"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Otvori birač vidžeta"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvori uređivač vidžeta"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Ukloni vidžet"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Dodaj vidžet"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zameni korisnika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 41d674416f86..ffde20e4d177 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-сувязь."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Значок прылады з Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Націсніце, каб задаць падрабязныя налады прылады"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Націсніце, каб пабачыць усе прылады"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Націсніце, каб спалучыць новую прыладу"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Працэнт зараду акумулятара невядомы."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Падлучаны да <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Ёсць падключэнне да <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Выкарыстоўваць Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Падключана"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Захавана"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"адключыць"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"актываваць"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Узровень зараду: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Гук"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе павольная зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Правядзіце пальцам па экране ўлева, каб азнаёміцца з дапаможнікам"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Адкрыць рэдактар віджэтаў"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Выдаліць віджэт"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Дадаць віджэт"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Перайсці да іншага карыстальніка"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"высоўнае меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Нядаўна выкарыстоўваўся праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Зараз выкарыстоўваецца праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Нядаўна выкарыстоўваўся праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Падсветка клавіятуры"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Узровень %1$d з %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 8f64831df295..ad690e657b0f 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е включен."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Икона за устройство с Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Кликнете, за да конфигурирате подробностите за устройството"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Кликнете, за да видите всички устройства"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Кликнете за сдвояване на ново устройство"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Процентът на батерията е неизвестен."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Има връзка с <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Установена е връзка с/ъс <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Използване на Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Установена е връзка"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Запазено"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекратяване на връзката"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активиране"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бавно • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Прекарайте пръст наляво, за да стартирате общия урок"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Отваряне на редактора на приспособлението"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Премахване на приспособление"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Добавяне на приспособлението"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Превключване между потребителите"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падащо меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Наскоро използвано от <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Използва се от <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Наскоро използвано от <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Подсветка на клавиатурата"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Ниво %1$d от %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 809a25d583bf..13837769a76e 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযুক্ত হয়েছে৷"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ব্লুটুথ ডিভাইসের আইকন"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ডিভাইসের বিবরণ কনফিগার করতে ক্লিক করুন"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"সব ডিভাইস দেখতে ক্লিক করুন"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"নতুন ডিভাইস পেয়ার করতে ক্লিক করুন"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ব্যাটারি কত শতাংশ আছে তা জানা যায়নি।"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>এ সংযুক্ত হয়ে আছে।"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> এর সাথে সংযুক্ত৷"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ব্লুটুথ ব্যবহার করুন"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"কানেক্ট করা আছে"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"সেভ করা আছে"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ডিসকানেক্ট করুন"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"চালু করুন"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"চার্জ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিও"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডসেট"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ধীরে চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চার্জ হচ্ছে • পুরো চার্জ হতে আরও <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> সময় লাগবে"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"কমিউনিটি টিউটোরিয়াল চালু করতে বাঁদিকে সোয়াইপ করুন"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"উইজেট এডিটর খুলুন"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"উইজেট সরিয়ে দিন"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"উইজেট যোগ করুন"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যবহারকারী পাল্টে দিন"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুলডাউন মেনু"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ ও ডেটা মুছে ফেলা হবে।"</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"সম্প্রতি <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ব্যবহার করা হয়েছে"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ব্যবহার করা হচ্ছে"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"সম্প্রতি <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ব্যবহার করা হয়েছে"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"কীবোর্ড ব্যাকলাইট"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d-এর মধ্যে %1$d লেভেল"</string> </resources> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index f97b05569e57..04812881c854 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je povezan."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona Bluetooth uređaja"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknite da konfigurirate detalje uređaja"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Pregled svih uređaja klikom"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Uparivanje novog uređaja klikom"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Postotak napunjenosti baterije nije poznat"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezan na <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Povezan na <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekid veze"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> baterije"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prevucite ulijevo da pokrenete zajednički vodič"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Otvaranje birača vidžeta"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvaranje uređivača vidžeta"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Uklanjanje vidžeta"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Dodajte vidžet"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zamijeni korisnika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci iz ove sesije će se izbrisati."</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index ba82dfe47298..03ef98d5820d 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connectat."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icona de dispositiu Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Fes clic per configurar els detalls del dispositiu"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Fes clic per veure tots els dispositius"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Fes clic per vincular un dispositiu nou"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Es desconeix el percentatge de bateria."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"S\'ha connectat a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Està connectat amb <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utilitza\'l"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connectat"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Desat"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconnecta"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activa"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Àudio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculars"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant lentament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • S\'està carregant • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Llisca cap a l\'esquerra per iniciar el tutorial de la comunitat"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Obre l\'editor de widgets"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Suprimeix un widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Afegeix un widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Canvia d\'usuari"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Utilitzat recentment per <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En ús per <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Utilitzat recentment per <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroil·luminació del teclat"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivell %1$d de %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 2201d64676b9..8b75a3e7bfe2 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Rozhraní Bluetooth je připojeno."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona zařízení Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknutím nakonfigurujete podrobnosti o zařízení"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknutím zobrazíte všechna zařízení"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknutím spárujete nové zařízení"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procento baterie není známé."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Připojeno k zařízení <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Jste připojeni k zařízení <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Použít Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Připojeno"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uloženo"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojit"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovat"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Sluchátka"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Pomalé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Přejetím doleva spustíte komunitní výukový program"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Otevřít výběr widgetu"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otevřít editor widgetů"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Odstranit widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Přidat widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Přepnout uživatele"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbalovací nabídka"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 14bce296477e..9f5b6008d425 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tilsluttet."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikon for Bluetooth-enhed"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klik for at konfigurere enhedsoplysninger"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klik for at se alle enheder"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klik for at parre en ny enhed"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batteriniveauet er ukendt."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Tilsluttet <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Forbundet til <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Brug Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Der er oprettet forbindelse"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gemt"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"afbryd forbindelse"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivér"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader langsomt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Stryg mod venstre for at starte den fælles vejledning"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Åbn redigeringsværktøjet til widgets"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Fjern en widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Tilføj widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skift bruger"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullemenu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Brugt for nylig af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Bruges af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Brugt for nylig af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Tastaturets baggrundslys"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Niveau %1$d af %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 2d9a83023018..25d7202436a6 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Mit Bluetooth verbunden"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Symbol des Bluetooth-Geräts"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klicke, um das Gerätedetail zu konfigurieren"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klicken, um alle Geräte anzeigen zu lassen"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klicken, um neues Gerät zu koppeln"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akkustand unbekannt."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Mit <xliff:g id="BLUETOOTH">%s</xliff:g> verbunden"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Verbunden mit <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth verwenden"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbunden"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gespeichert"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Verknüpfung aufheben"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivieren"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkustand: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -404,10 +400,10 @@ <string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird schnell geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird langsam geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> - <!-- no translation found for communal_tutorial_indicator_text (4503010353591430123) --> - <skip /> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Widget-Auswahl öffnen"</string> + <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Wische nach links, um das gemeinsame Tutorial zu starten"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Widget-Editor öffnen"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Widget entfernen"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Widget hinzufügen"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Nutzer wechseln"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Pull-down-Menü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 72fe0804889f..9bd246a64668 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Το Bluetooth είναι συνδεδεμένο."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Εικονίδιο συσκευής Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Κάντε κλικ για να διαμορφώσετε τις λεπτομέρειες συσκευής"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Κάντε κλικ για εμφάνιση όλων των συσκευών"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Κάντε κλικ για σύζευξη νέας συσκευής"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Άγνωστο ποσοστό μπαταρίας."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Συνδέθηκε στο <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Συνδέθηκε σε <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Χρήση Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Συνδέθηκε"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Αποθηκεύτηκε"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"αποσύνδεση"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ενεργοποίηση"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Μπαταρία <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ήχος"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ακουστικά"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Αργή φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Σύρετε προς τα αριστερά για να ξεκινήσετε τον κοινόχρηστο οδηγό"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Άνοιγμα του εργαλείου επιλογής γραφικών στοιχείων"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Άνοιγμα προγράμ. επεξεργασίας γραφικών στοιχείων"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Αφαίρεση ενός widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Προσθήκη γραφικού στοιχείου"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Εναλλαγή χρήστη"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"αναπτυσσόμενο μενού"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index d7062e157c4a..b3dea8d4a666 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth device icon"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Click to configure device detail"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Click to see all devices"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Click to pair new device"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Open the widget picker"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Remove a widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Add Widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 9fcf1a3fc68e..bbfdcea79a24 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth device icon"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Click to configure device detail"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Click to see all devices"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Click to pair new device"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Open the widget picker"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Remove a widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Add Widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index d7062e157c4a..b3dea8d4a666 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth device icon"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Click to configure device detail"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Click to see all devices"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Click to pair new device"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Open the widget picker"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Remove a widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Add Widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index d7062e157c4a..b3dea8d4a666 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth device icon"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Click to configure device detail"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Click to see all devices"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Click to pair new device"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Open the widget picker"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Remove a widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Add Widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index a68c7c557fcf..5340b7bd2d37 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth device icon"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Click to configure device detail"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Click to see all devices"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Click to pair new device"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Open the widget picker"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Remove a widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Add Widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 03acc6a43cc2..78f91b192b28 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ícono de dispositivo Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Haz clic para configurar los detalles del dispositivo"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Haz clic para ver todos los dispositivos"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Haz clic para vincular un dispositivo nuevo"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Se desconoce el porcentaje de la batería."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lento • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Desliza el dedo a la izquierda para iniciar el instructivo comunal"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Abre el selector de widgets"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir el editor de widget"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Quita el widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Agregar widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú expandible"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 18131372ec40..59802adc4617 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icono de dispositivo Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Haz clic para configurar la información del dispositivo"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Haz clic para ver todos los dispositivos"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Haz clic para emparejar un nuevo dispositivo"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentaje de batería desconocido."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Carga completa en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Desliza hacia la izquierda para iniciar el tutorial de la comunidad"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir editor de widgets"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Eliminar un widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Añadir widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar de usuario"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string> @@ -1081,7 +1076,7 @@ <string name="person_available" msgid="2318599327472755472">"Disponible"</string> <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"No se ha podido leer el indicador de batería"</string> <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca la pantalla para consultar más información"</string> - <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ninguna alarma puesta"</string> + <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ninguna puesta"</string> <string name="accessibility_bouncer" msgid="5896923685673320070">"Poner bloqueo de pantalla"</string> <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de huellas digitales"</string> <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticarte"</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Usado recientemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Usado recientemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroiluminación del teclado"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivel %1$d de %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 57dbb96c7143..ec65920ff73c 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth on ühendatud."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth-seadme ikoon"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klõpsake seadme üksikasjade konfigureerimiseks"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kõigi seadmete kuvamiseks klõpsake"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Uue seadme sidumiseks klõpsake"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Aku laetuse protsent on teadmata."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ühendatud: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Ühendatud ülekandega <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Kasuta Bluetoothi"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ühendatud"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvestatud"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkesta ühendus"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveeri"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> akut"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Heli"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Peakomplekt"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Aeglane laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ühise õpetuse käivitamiseks pühkige vasakule"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidina redaktori avamine"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Eemalda vidin"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Lisa vidin"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kasutaja vahetamine"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rippmenüü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Kasutas hiljuti rakendus <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Seda kasutab <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Kasutas hiljuti rakendus <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Klaviatuuri taustavalgustus"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Tase %1$d/%2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 0902354bfa82..b5a12cfdf468 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetootha konektatuta."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth bidezko gailuaren ikonoa"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Gailuaren xehetasuna konfiguratzeko, sakatu hau"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Egin klik gailu guztiak ikusteko"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Egin klik beste gailu bat parekatzeko"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Bateriaren ehunekoa ezezaguna da."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> gailura konektatuta."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Hona konektatuta: <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Erabili Bluetootha"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Konektatuta"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gordeta"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deskonektatu"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktibatu"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audioa"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Entzungailua"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mantso kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Tutorial komuna hasteko, pasatu hatza ezkerrera"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ireki widget-editorea"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Kendu widget bat"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Gehitu widgeta"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Aldatu erabiltzailea"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"zabaldu menua"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) aplikazioak erabili du duela gutxi"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak darabil (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) aplikazioak erabili du duela gutxi"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Teklatuaren hondoko argia"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d/%2$d maila"</string> </resources> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index f42bf5038298..055ea3beea74 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوتوث متصل است."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"نماد دستگاه بلوتوث"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"برای پیکربندی جزئیات دستگاه کلیک کنید"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"برای دیدن همه دستگاهها، کلیک کنید"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"برای جفت کردن دستگاه جدید، کلیک کنید"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"درصد شارژ باتری مشخص نیست."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"به <xliff:g id="BLUETOOTH">%s</xliff:g> متصل شد."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"به <xliff:g id="CAST">%s</xliff:g> متصل شد."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"استفاده از بلوتوث"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"متصل"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ذخیرهشده"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"قطع اتصال"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کردن"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"شارژ باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"هدست"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ کردن آهسته • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ شدن • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"برای شروع آموزش گامبهگام عمومی، تند بهچپ بکشید"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"باز کردن انتخابگر ابزارک"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"باز کردن ویرایشگر ابزارک"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"حذف ابزارک"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"افزودن ابزارک"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تغییر کاربر"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"منوی پایینپر"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامهها و دادههای این جلسه حذف خواهد شد."</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index ae3ac060d494..82e5231ff976 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth yhdistetty."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth-laitekuvake"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Määritä laitteen asetukset klikkaamalla"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Katso kaikki laitteet klikkaamalla"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Muodosta uusi laitepari klikkaamalla"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akun varaustaso ei tiedossa."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Yhteys: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Yhdistetty kohteeseen <xliff:g id="CAST">%s</xliff:g>"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Käytä Bluetoothia"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Yhdistetty"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Tallennettu"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkaise yhteys"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivoi"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akun taso <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ääni"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu hitaasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Aloita yhteisöesittely pyyhkäisemällä vasemmalle"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Avaa widgetien muokkaaja"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Poista widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Lisää widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Vaihda käyttäjää"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"alasvetovalikko"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> käytti tätä äskettäin (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Tämän käytössä: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> käytti tätä äskettäin (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Näppämistön taustavalo"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Taso %1$d/%2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 286753d01041..619c7f88da58 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icône de l\'appareil Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Cliquez pour configurer les détails de l\'appareil"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Cliquez ici pour voir tous les appareils"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Cliquez ici pour associer un nouvel appareil"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pourcentage de la pile inconnu."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connecté à <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Déconnecter"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"Activer"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Pile : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Écouteurs"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"En recharge lente : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge en cours… • Se terminera dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Balayer l\'écran vers la gauche pour démarrer le tutoriel communautaire"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ouvrir l\'éditeur de widget"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Retirez le widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Ajouter un widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Rétroéclairage du clavier"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Niveau %1$d de %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 7f4e4cc36d5c..9f22a57fe2b3 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icône de l\'appareil Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Cliquer pour configurer les détails de l\'appareil"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Cliquer pour afficher tous les appareils"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Cliquer pour associer un nouvel appareil"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pourcentage de la batterie inconnu."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connecté à <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"dissocier"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activer"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batterie"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Casque"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge lente • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Balayer vers la gauche pour démarrer le tutoriel collectif"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Ouvrez le sélecteur de widgets"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ouvrir l\'éditeur de widgets"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Retirez un widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Ajouter le widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 88d0a314789d..a7341b0d9926 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icona do dispositivo Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Facer clic para configurar os detalles do dispositivo"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Facer clic para ver todos os dispositivos"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Facer clic para vincular un novo dispositivo"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Descoñécese a porcentaxe da batería."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Dispositivo conectado: <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Estableceuse a conexión"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gardouse"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lentamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Pasa o dedo cara á esquerda para iniciar o titorial comunitario"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Quitar un widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Engadir widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú despregable"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"En uso recentemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"En uso recentemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroiluminación do teclado"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivel %1$d de %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 92638a22fc79..2ca21c537507 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"બ્લૂટૂથ કનેક્ટ થયું."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"બ્લૂટૂથ ડિવાઇસનું આઇકન"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ડિવાઇસની વિગત ગોઠવવા માટે ક્લિક કરો"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"બધા ડિવાઇસ જોવા માટે ક્લિક કરો"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"નવું ડિવાઇસ જોડવા માટે ક્લિક કરો"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"બૅટરીની ટકાવારી અજાણ છે."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> થી કનેક્ટ થયાં."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> થી કનેક્ટ કરેલ."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"બ્લૂટૂથનો ઉપયોગ કરો"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"કનેક્ટેડ છે"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"સાચવેલું"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ડિસ્કનેક્ટ કરો"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"સક્રિય કરો"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> બૅટરી"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ઑડિયો"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"હૅડસેટ"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ધીમેથી ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં ચાર્જ થઈ જશે"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં પૂરું ચાર્જ થઈ જશે"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"કૉમ્યુનલ ટ્યૂટૉરિઅલ શરૂ કરવા માટે ડાબે સ્વાઇપ કરો"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"વિજેટ પિકર ખોલો"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"વિજેટ એડિટર ખોલો"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"કોઈ વિજેટ કાઢી નાખો"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"વિજેટ ઉમેરો"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"વપરાશકર્તા સ્વિચ કરો"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"પુલડાઉન મેનૂ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ અને ડેટા કાઢી નાખવામાં આવશે."</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 213457fef803..6a7d143601ba 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट किया गया."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ब्लूटूथ डिवाइस का आइकॉन"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"डिवाइस की जानकारी कॉन्फ़िगर करने के लिए क्लिक करें"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"\'सभी डिवाइस देखें\' पर क्लिक करें"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"\'नया डिवाइस जोड़ें\' पर क्लिक करें"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"इस बारे में जानकारी नहीं है कि अभी बैटरी कितने प्रतिशत चार्ज है."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> से कनेक्ट किया गया."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> से कनेक्ट है."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लूटूथ इस्तेमाल करें"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट है"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव किया गया"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिसकनेक्ट करें"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"चालू करें"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बैटरी"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडियो"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • धीरे चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"कम्यूनिटी ट्यूटोरियल शुरू करने के लिए, बाईं ओर स्वाइप करें"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"विजेट पिकर को खोलें"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट एडिटर खोलें"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"विजेट को हटाएं"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"विजेट जोड़ें"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सेशन के सभी ऐप्लिकेशन और डेटा को हटा दिया जाएगा."</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index cf86c466b645..4469d788be61 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth povezan."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona Bluetooth uređaja"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknite da biste konfigurirali pojedinosti o uređaju"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknite za prikaz svih uređaja"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknite da biste uparili novi uređaj"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Postotak baterije nije poznat."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Spojen na <xliff:g id="BLUETOOTH">%s</xliff:g> ."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Povezani ste sa sljedećim uređajem: <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Uključi"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Spremljeno"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekini vezu"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviraj"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> baterije"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • sporo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prijeđite prstom ulijevo da biste pokrenuli zajednički vodič"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Otvaranje alata za odabir widgeta"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvaranje alata za uređivanje widgeta"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Uklanjanje widgeta"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Dodaj widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Promjena korisnika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući izbornik"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Izbrisat će se sve aplikacije i podaci u ovoj sesiji."</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 6f1d2c609bb0..51c3932261a9 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth csatlakoztatva."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth-eszköz ikon"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kattintson az eszköz beállításainak megadásához"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kattintson az összes eszköz megtekintéséhez"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kattintson új eszköz párosításához"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Az akkumulátor töltöttségi szintje ismeretlen."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Csatlakoztatva a következőhöz: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Csatlakozva a következőhöz: <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth használata"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Csatlakozva"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Mentve"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"leválasztás"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiválás"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkumulátor: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hang"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lassú töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Csúsztasson gyorsan balra a közösségi útmutató elindításához"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"A modulválasztó megnyitása"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"A modulszerkesztő megnyitása"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"A modul eltávolítása"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Modul hozzáadása"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Felhasználóváltás"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"lehúzható menü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 8ceeb4b187bb..917fb77de826 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-ը միացված է:"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth սարքի պատկերակ"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Սեղմեք՝ սարքի մանրամասները կազմաձևելու համար"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Սեղմեք՝ բոլոր սարքերը տեսնելու համար"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Սեղմեք՝ նոր սարք զուգակցելու համար"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Մարտկոցի լիցքի մակարդակն անհայտ է։"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Միացված է <xliff:g id="BLUETOOTH">%s</xliff:g>-ին:"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Միացված է <xliff:g id="CAST">%s</xliff:g>-ին:"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Միացնել Bluetooth-ը"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Միացված է"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Պահված է"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"անջատել"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ակտիվացնել"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Մարտկոցի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Աուդիո"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ականջակալ"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Դանդաղ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Թերթեք ձախ՝ ուղեցույցը գործարկելու համար"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Բացել վիջեթների խմբագրիչը"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Հեռացնել վիջեթը"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Ավելացնել վիջեթ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Անջատել օգտվողին"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"իջնող ընտրացանկ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն:"</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Վերջերս օգտագործվել է <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի կողմից (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Օգտագործվում է <xliff:g id="APP_NAME">%1$s</xliff:g>ի կողմից (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Վերջերս օգտագործվել է <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի կողմից (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Հետին լուսավորությամբ ստեղնաշար"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d՝ %2$d-ից"</string> </resources> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 285bb3935b33..83db83b6ce80 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth terhubung."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikon perangkat Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klik untuk mengonfigurasi detail perangkat"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klik untuk melihat semua perangkat"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klik untuk menyambungkan perangkat baru"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Persentase baterai tidak diketahui."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Terhubung ke <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Terhubung ke <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Terhubung"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan koneksi"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan lambat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Geser ke kiri untuk memulai tutorial komunal"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buka editor widget"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Hapus widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Tambahkan Widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Beralih pengguna"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pulldown"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua aplikasi dan data dalam sesi ini akan dihapus."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Baru saja digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Sedang digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Baru saja digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Lampu latar keyboard"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Tingkat %1$d dari %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 2cf9237766ac..bf0b73d6d86c 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tengt."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Tákn Bluetooth-tækis"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Smelltu til að stilla tækjaupplýsingar"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Smelltu til að sjá öll tæki"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Smelltu til að para nýtt tæki"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Staða rafhlöðu óþekkt."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Tengt við <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Tengt við <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Nota Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tengt"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Vistað"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"aftengja"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"virkja"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> rafhlöðuhleðsla"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hljóð"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Höfuðtól"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hæg hleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Í hleðslu • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Strjúktu til vinstri til að hefja samfélagsleiðsögnina"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Opna græjuritilinn"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Fjarlægja græju"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Bæta græju við"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skipta um notanda"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Fellivalmynd"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nýlega notað af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Í notkun í <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nýlega notað af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Baklýsing lyklaborðs"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Stig %1$d af %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 4912a53f8c6d..e65e3aa29ab1 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth collegato."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icona del dispositivo Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Fai clic per configurare i dettagli del dispositivo"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Fai clic per vedere tutti i dispositivi"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Fai clic per accoppiare un nuovo dispositivo"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percentuale della batteria sconosciuta."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connesso a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connesso a: <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usa Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Dispositivo connesso"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Dispositivo salvato"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnetti"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"attiva"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batteria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auricolare"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica lenta • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • In carica • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Scorri a sinistra per iniziare il tutorial della community"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Apri l\'editor del widget"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Rimuovi un widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Aggiungi widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambio utente"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu a discesa"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tutte le app e i dati di questa sessione verranno eliminati."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recentemente in uso da <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"In uso da <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recentemente in uso da <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroilluminazione della tastiera"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Livello %1$d di %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index eef614225afe..a14aa1b91b57 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth מחובר."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"סמל של מכשיר Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"יש ללחוץ כדי להגדיר את פרטי המכשיר"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"אפשר ללחוץ כדי לראות את כל המכשירים"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"צריך ללחוץ כדי להתאים מכשיר חדש"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"אחוז טעינת הסוללה לא ידוע."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"התבצע חיבור אל <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"מחובר אל <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"מחובר"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"נשמר"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ניתוק"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"הפעלה"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> סוללה"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"אודיו"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"אוזניות"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה איטית • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"אפשר להחליק שמאלה כדי להפעיל את המדריך המשותף"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"פתיחה של הכלי לעריכת ווידג\'טים"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"הסרה של ווידג\'ט"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"הוספת ווידג\'ט"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"החלפת משתמש"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"תפריט במשיכה למטה"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"כל האפליקציות והנתונים בסשן הזה יימחקו."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"נעשה שימוש לאחרונה על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"בשימוש על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"נעשה שימוש לאחרונה על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"התאורה האחורית במקלדת"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"רמה %1$d מתוך %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index eaf719a49f36..75166d9546ad 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetoothに接続済み。"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth デバイスのアイコン"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"クリックしてデバイスの詳細を設定します"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"クリックすると、すべてのデバイスが表示されます"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"クリックすると、新しいデバイスをペア設定できます"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"バッテリー残量は不明です。"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>に接続しました。"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>に接続されています。"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth を使用"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"接続しました"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存しました"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"接続を解除"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"有効化"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"バッテリー <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"オーディオ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ヘッドセット"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 低速充電中 • 完了まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • フル充電まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"左にスワイプすると、コミュニティ チュートリアルが開始します"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"ウィジェット選択ツールを開きます"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ウィジェット エディタを開く"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"ウィジェットを削除します"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ウィジェットを追加"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ユーザーを切り替える"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"プルダウン メニュー"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index b3295e3220f9..b363f993338b 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth დაკავშირებულია."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth მოწყობილობის ხატულა"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"დააწკაპუნეთ მოწყობილობის დეტალების კონფიგურირებისთვის"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"დააწკაპუნეთ ყველა მოწყობილობის სანახავად"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"დააწკაპუნეთ ახალი მოწყობილობის დასაწყვილებლად"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ბატარეის პროცენტული მაჩვენებელი უცნობია."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"დაკავშირებულია <xliff:g id="BLUETOOTH">%s</xliff:g>-თან."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"დაკავშირებულია მოწყობილობასთან: <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ის გამოყენება"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"დაკავშირებული"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"შენახული"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"კავშირის გაწყვეტა"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"გააქტიურება"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ბატარეა"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"აუდიო"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ყურსაცვამი"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ნელა იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"გადაფურცლეთ მარცხნივ, რათა დაიწყოთ საერთო სახელმძღვანელო"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"გახსენით ვიჯეტის რედაქტორი"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"ამოშალეთ ვიჯეტი"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ვიჯეტის დამატება"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"მომხმარებლის გადართვა"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ჩამოშლადი მენიუ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ახლახან გამოყენებულია <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"გამოიყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ახლახან გამოყენებულია <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"კლავიატურის შენათება"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"დონე: %1$d %2$d-დან"</string> </resources> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 3dc2ebcee7b6..b887e4b28508 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth қосылған."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth құрылғысы белгішесі"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Құрылғы деректерін конфигурациялау үшін басыңыз."</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Барлық құрылғыны көру үшін басыңыз."</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Жаңа құрылғы жұптау үшін басыңыз."</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея зарядының мөлшері белгісіз."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> қосылған."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> трансляциясына қосылды."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ты пайдалану"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Қосылды"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сақталды"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажырату"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"іске қосу"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батарея деңгейі: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Aудио"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Баяу зарядталуда • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядталып жатыр. • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ортақ оқулықты ашу үшін солға қарай сырғытыңыз."</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет редакторын ашу"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Виджетті өшіру"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Виджет қосу"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Пайдаланушыны ауыстыру"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ашылмалы мәзір"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданба мен дерек жойылады."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Соңғы рет <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) қолданбасы пайдаланды."</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) қолданбасы пайдаланып жатыр"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Соңғы рет <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) қолданбасы пайдаланды."</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Пернетақта жарығы"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Деңгей: %1$d/%2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 6ba41b83d4ed..0a4c2d8ae0e3 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"បានតភ្ជាប់ប៊្លូធូស។"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"រូបឧបករណ៍ប៊្លូធូស"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ចុចដើម្បីកំណត់រចនាសម្ព័ន្ធព័ត៌មានលម្អិតអំពីឧបករណ៍"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"ចុច ដើម្បីមើលឃើញឧបករណ៍ទាំងអស់"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"ចុច ដើម្បីផ្គូផ្គងឧបករណ៍ថ្មី"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"មិនដឹងអំពីភាគរយថ្មទេ។"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"បានភ្ជាប់ទៅ <xliff:g id="BLUETOOTH">%s</xliff:g> ។"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"បានភ្ជាប់ទៅ <xliff:g id="CAST">%s</xliff:g>"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ប្រើប៊្លូធូស"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"បានភ្ជាប់"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"បានរក្សាទុក"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ផ្ដាច់"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"បើកដំណើរការ"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"ថ្ម <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"សំឡេង"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"កាស"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្មយឺត • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្ម • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"អូសទៅឆ្វេង ដើម្បីចាប់ផ្ដើមមេរៀនសហគមន៍"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"បើកផ្ទាំងជ្រើសរើសធាតុក្រាហ្វិក"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"បើកកម្មវិធីកែធាតុក្រាហ្វិក"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"ដកធាតុក្រាហ្វិកចេញ"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"បញ្ចូលធាតុក្រាហ្វិក"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ប្ដូរអ្នកប្រើ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ម៉ឺនុយទាញចុះ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"កម្មវិធី និងទិន្នន័យទាំងអស់ក្នុងវគ្គនេះនឹងត្រូវលុប។"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 080ce108e46c..850bb0ad5f8f 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ಬ್ಲೂಟೂತ್ ಸಂಪರ್ಕಗೊಂಡಿದೆ."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ಬ್ಲೂಟೂತ್ ಸಾಧನ ಐಕಾನ್"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ಸಾಧನದ ವಿವರಗಳನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"ಎಲ್ಲಾ ಸಾಧನಗಳನ್ನು ನೋಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"ಹೊಸ ಸಾಧನವನ್ನು ಪೇರ್ ಮಾಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ಬ್ಯಾಟರಿ ಶೇಕಡಾವಾರು ತಿಳಿದಿಲ್ಲ."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ಬ್ಲೂಟೂತ್ ಬಳಸಿ"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ಡಿಸ್ಕನೆಕ್ಟ್ ಮಾಡಿ"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ಸಕ್ರಿಯಗೊಳಿಸಿ"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ಬ್ಯಾಟರಿ"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ಆಡಿಯೋ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ಹೆಡ್ಸೆಟ್"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ನಿಧಾನವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ಸಮುದಾಯದ ಟ್ಯುಟೋರಿಯಲ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಎಡಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"ವಿಜೆಟ್ ಪಿಕರ್ ತೆರೆಯಿರಿ"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ವಿಜೆಟ್ ಎಡಿಟರ್ ಅನ್ನು ತೆರೆಯಿರಿ"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"ವಿಜೆಟ್ ತೆಗೆದುಹಾಕಿ"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ವಿಜೆಟ್ ಸೇರಿಸಿ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ಬಳಕೆದಾರರನ್ನು ಬದಲಿಸಿ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ಪುಲ್ಡೌನ್ ಮೆನು"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಷನ್ನಲ್ಲಿನ ಎಲ್ಲ ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string> @@ -745,7 +742,7 @@ <item msgid="7453955063378349599">"ಎಡ-ಬಾಗುವಿಕೆ"</item> <item msgid="5874146774389433072">"ಬಲ-ಬಾಗುವಿಕೆ"</item> </string-array> - <string name="save" msgid="3392754183673848006">"ಉಳಿಸಿ"</string> + <string name="save" msgid="3392754183673848006">"ಸೇವ್ ಮಾಡಿ"</string> <string name="reset" msgid="8715144064608810383">"ಮರುಹೊಂದಿಸಿ"</string> <string name="clipboard" msgid="8517342737534284617">"ಕ್ಲಿಪ್ಬೋರ್ಡ್"</string> <string name="accessibility_key" msgid="3471162841552818281">"ಕಸ್ಟಮ್ ನ್ಯಾವಿಗೇಷನ್ ಬಟನ್"</string> @@ -1032,7 +1029,7 @@ <string name="media_output_broadcasting_message" msgid="4150299923404886073">"ನಿಮ್ಮ ಪ್ರಸಾರವನ್ನು ಆಲಿಸಲು, ಹೊಂದಾಣಿಕೆಯಾಗುವ ಬ್ಲೂಟೂತ್ ಸಾಧನಗಳನ್ನು ಹೊಂದಿರುವ ಸಮೀಪದಲ್ಲಿರುವ ಜನರು ನಿಮ್ಮ QR ಕೋಡ್ ಅನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಬಹುದು ಅಥವಾ ನಿಮ್ಮ ಪ್ರಸಾರದ ಹೆಸರು ಹಾಗೂ ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಬಳಸಬಹುದು"</string> <string name="media_output_broadcast_name" msgid="8786127091542624618">"ಪ್ರಸಾರದ ಹೆಸರು"</string> <string name="media_output_broadcast_code" msgid="870795639644728542">"ಪಾಸ್ವರ್ಡ್"</string> - <string name="media_output_broadcast_dialog_save" msgid="7910865591430010198">"ಉಳಿಸಿ"</string> + <string name="media_output_broadcast_dialog_save" msgid="7910865591430010198">"ಸೇವ್ ಮಾಡಿ"</string> <string name="media_output_broadcast_starting" msgid="8130153654166235557">"ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ…"</string> <string name="media_output_broadcast_start_failed" msgid="3670835946856129775">"ಪ್ರಸಾರ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="media_output_broadcast_update_error" msgid="1420868236079122521">"ಉಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 917781662a20..a9395b2fadaf 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"블루투스가 연결되었습니다."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"블루투스 기기 아이콘"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"기기 세부정보를 구성하려면 클릭하세요."</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"모든 기기를 보려면 클릭하세요"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"새 기기를 페어링하려면 클릭하세요"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"배터리 잔량을 알 수 없습니다."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>에 연결되었습니다."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>에 연결됨"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"블루투스 사용"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"연결됨"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"저장됨"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"연결 해제"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"실행"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"배터리 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"오디오"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"헤드셋"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 저속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"공동 튜토리얼을 시작하려면 왼쪽으로 스와이프하세요"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"위젯 편집기 열기"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"위젯 삭제"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"위젯 추가"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"사용자 전환"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"풀다운 메뉴"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"최근 <xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용됨(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용 중(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"최근 <xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용됨(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"키보드 백라이트"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d단계 중 %1$d단계"</string> </resources> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index e58cf593bde7..73e76477468c 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth байланышта"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth түзмөгүнүн сүрөтчөсү"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Түзмөктүн чоо-жайын конфигурациялоо үчүн чыкылдатыңыз"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Бардык түзмөктөрдү көрүү үчүн чыкылдатыңыз"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Жаңы түзмөктү жупташтыруу үчүн чыкылдатыңыз"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея кубатынын деңгээли белгисиз."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> менен туташкан."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> менен туташты."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Иштетүү"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Туташты"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сакталды"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажыратуу"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"иштетүү"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батареянын деңгээли <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жай кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Жалпы үйрөткүчтү иштетүү үчүн солго сүрүңүз"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет түзөткүчтү ачуу"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Виджетти өчүрүү"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Виджет кошуу"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Колдонуучуну которуу"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ылдый түшүүчү меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана аларга байланыштуу нерселер өчүрүлөт."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Акыркы жолу <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) колдонмосунда иштетилди"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда иштетилип жатат (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Акыркы жолу <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) колдонмосунда иштетилди"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Баскычтоптун жарыгы"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d ичинен %1$d-деңгээл"</string> </resources> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 0c220cce1b86..e9f6a19b5ec2 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ເຊື່ອມຕໍ່ Bluetooth ແລ້ວ."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ໄອຄອນອຸປະກອນ Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ຄລິກເພື່ອຕັ້ງຄ່າລາຍລະອຽດອຸປະກອນ"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"ຄລິກເພື່ອເບິ່ງອຸປະກອນທັງໝົດ"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"ຄລິກເພື່ອຈັບຄູ່ອຸປະກອນໃໝ່"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ບໍ່ຮູ້ເປີເຊັນແບັດເຕີຣີ."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ເຊື່ອມຕໍ່ຫາ <xliff:g id="BLUETOOTH">%s</xliff:g> ແລ້ວ."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"ເຊື່ອມຕໍ່ຫາ <xliff:g id="CAST">%s</xliff:g> ແລ້ວ."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ໃຊ້ Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ເຊື່ອມຕໍ່ແລ້ວ"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ບັນທຶກແລ້ວ"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ຕັດການເຊື່ອມຕໍ່"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ເປີດນຳໃຊ້"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"ແບັດເຕີຣີ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ສຽງ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ຊຸດຫູຟັງ"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟແບບຊ້າ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ປັດຊ້າຍເພື່ອເລີ່ມບົດແນະນຳສ່ວນກາງ"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ເປີດຕົວແກ້ໄຂວິດເຈັດ"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"ລຶບວິດເຈັດອອກ"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ເພີ່ມວິດເຈັດ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ສະຫຼັບຜູ້ໃຊ້"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ເມນູແບບດຶງລົງ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯແລະຂໍ້ມູນທັງໝົດໃນເຊດຊັນນີ້ຈະຖືກລຶບອອກ."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ໃຊ້ຫຼ້າສຸດໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"ໃຊ້ຢູ່ໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ໃຊ້ຫຼ້າສຸດໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"ໄຟປຸ່ມແປ້ນພິມ"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"ລະດັບທີ %1$d ຈາກ %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index b1139a2bd4fc..0e9e94018e48 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"„Bluetooth“ prijungtas."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"„Bluetooth“ įrenginio piktograma"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Spustelėkite, jei norite konfigūruoti išsamią įrenginio informaciją"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Spustelėkite, kad peržiūrėtumėte visus įrenginius"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Spustelėkite, kad susietumėte naują įrenginį"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akumuliatoriaus energija procentais nežinoma."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Prisijungta prie „<xliff:g id="BLUETOOTH">%s</xliff:g>“."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Prisijungta prie <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"„Bluetooth“ naudojimas"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Prisijungta"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Išsaugota"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atjungti"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"suaktyvinti"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumuliatorius: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Garsas"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Virtualiosios realybės įrenginys"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lėtai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Perbraukite kairėn, paleistumėte bendruomenės mokomąją medžiagą"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Atidaryti valdiklio redagavimo programą"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Pašalinti valdiklį"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Pridėti valdiklį"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Perjungti naudotoją"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"išplečiamasis meniu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Neseniai naudojo „<xliff:g id="APP_NAME">%1$s</xliff:g>“ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Naudoja <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Neseniai naudojo „<xliff:g id="APP_NAME">%1$s</xliff:g>“ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Klaviatūros foninis apšvietimas"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d lygis iš %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 03d6237f9125..8c7af638f875 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth savienojums ir izveidots."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth ierīces ikona"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Lai konfigurētu ierīces informāciju, noklikšķiniet"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Noklikšķiniet, lai skatītu visas ierīces"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Noklikšķiniet, lai savienotu pārī jaunu ierīci"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akumulatora uzlādes līmenis procentos nav zināms."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ir izveidots savienojum ar <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Savienots ar ierīci <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Izmantot Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Savienojums izveidots"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saglabāta"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atvienot"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizēt"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumulators: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Austiņas"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lēnā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Notiek uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Velciet pa kreisi, lai palaistu kopienas pamācību."</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Atvērt logrīku redaktoru"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Noņemt logrīku"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Pievienot logrīku"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mainīt lietotāju"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"novelkamā izvēlne"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nesen to izmantoja lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"To izmanto lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nesen to izmantoja lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Tastatūras fona apgaismojums"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Līmenis numur %1$d, kopā ir %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 3374ba335b44..6c577ce01713 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е поврзан."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Икона за уред со Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Кликнете за да ги конфигурирате деталите за уредот"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Кликнете за да се прикажат сите уреди"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Кликнете за да спарите нов уред"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Процентот на батеријата е непознат."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Поврзано со <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Поврзано со <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Користи Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Поврзано"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Зачувано"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекини врска"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирај"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> батерија"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string> @@ -405,9 +401,10 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни бавно • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Повлечете налево за да го започнете заедничкото упатство"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> + <!-- no translation found for button_to_open_widget_editor (5599945944349057600) --> <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Отстранува виџет"</string> + <!-- no translation found for hub_mode_add_widget_button_text (3956587989338301487) --> <skip /> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Промени го корисникот"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"паѓачко мени"</string> @@ -1217,8 +1214,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Неодамна користено од <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Се користи од <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Неодамна користено од <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Осветлување на тастатура"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Ниво %1$d од %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 6e70f84df206..7354e914f558 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ബ്ലൂടൂത്ത് കണക്റ്റുചെയ്തു."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth ഉപകരണ ഐക്കൺ"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ഉപകരണത്തിന്റെ വിശദാംശങ്ങൾ കോൺഫിഗർ ചെയ്യാൻ ക്ലിക്ക് ചെയ്യുക"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"എല്ലാ ഉപകരണങ്ങളും കാണാൻ ക്ലിക്ക് ചെയ്യുക"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"പുതിയ ഉപകരണം ജോടിയാക്കാൻ ക്ലിക്ക് ചെയ്യുക"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ബാറ്ററി ശതമാനം അജ്ഞാതമാണ്."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തു."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തു."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ഉപയോഗിക്കുക"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"കണക്റ്റ് ചെയ്തു"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"സംരക്ഷിച്ചു"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"വിച്ഛേദിക്കുക"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"സജീവമാക്കുക"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ബാറ്ററി"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ഓഡിയോ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ഹെഡ്സെറ്റ്"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • പതുക്കെ ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"കമ്മ്യൂണൽ ട്യൂട്ടോറിയൽ ആരംഭിക്കാൻ ഇടത്തോട്ട് സ്വൈപ്പ് ചെയ്യുക"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"വിജറ്റ് പിക്കർ തുറക്കുക"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"വിജറ്റ് എഡിറ്റർ തുറക്കുക"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"വിജറ്റ് നീക്കം ചെയ്യുക"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"വിജറ്റ് ചേർക്കുക"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ഉപയോക്താവ് മാറുക"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"പുൾഡൗൺ മെനു"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 93538c119eb6..e6dc9e55383b 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth холбогдсон."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth төхөөрөмжийн дүрс тэмдэг"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Төхөөрөмжийн дэлгэрэнгүйг тохируулахын тулд товшино уу"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Бүх төхөөрөмжийг харахын тулд товшино уу"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Шинэ төхөөрөмж хослуулахын тулд товшино уу"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарейн хувь тодорхойгүй байна."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>-тай холбогдсон."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>-д холбогдсон."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-г ашиглах"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Холбогдсон"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Хадгалсан"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"салгах"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"идэвхжүүлэх"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> батарей"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Чихэвч"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Удаан цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Нийтийн практик хичээлийг эхлүүлэхийн тулд зүүн тийш шударна уу"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет засварлагчийг нээх"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Виджетийг хасах"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Виджет нэмэх"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Хэрэглэгчийг сэлгэх"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"эвхмэл цэс"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ харилцан үйлдлийн бүх апп болон дата устах болно."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Саяхан <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ашигласан"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ашиглаж байна"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Саяхан <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ашигласан"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Гарын арын гэрэл"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d-с %1$d-р түвшин"</string> </resources> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 32c27ac0e828..a5d0afa965af 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट केले."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ब्लूटूथ डिव्हाइस आयकन"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"डिव्हाइसचे तपशील कॉंफिगर करण्यासाठी क्लिक करा"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"सर्व डिव्हाइस पाहण्यासाठी क्लिक करा"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"नवीन डिव्हाइस पेअर करण्यासाठी क्लिक करा"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"बॅटरीच्या चार्जिंगची टक्केवारी माहित नाही."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> शी कनेक्ट केले."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> शी कनेक्ट केले."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लूटूथ वापरा"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट केले"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव्ह केले"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट करा"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ॲक्टिव्हेट करा"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बॅटरी"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडिओ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • हळू चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"सामुदायिक ट्यूटोरियल सुरू करण्यासाठी डावीकडे स्वाइप करा"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"विजेट पिकर उघडा"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट संपादक उघडा"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"विजेट काढून टाका"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"विजेट जोडा"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"वापरकर्ता स्विच करा"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनू"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अॅप्स आणि डेटा हटवला जाईल."</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 62efce1cd8f0..bf0a60cc7044 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth disambungkan."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikon peranti Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klik untuk mengkonfigurasi butiran peranti"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klik untuk melihat semua peranti"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klik untuk menggandingkan peranti baharu"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Peratusan kuasa bateri tidak diketahui."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Disambungkan kepada <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Disambungkan ke <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Disambungkan"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan sambungan"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Set Kepala"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas dengan perlahan • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Leret ke kiri untuk memulakan tutorial umum"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Buka pemilih widget"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buka editor widget"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Alih keluar widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Tambahkan Widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Tukar pengguna"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu tarik turun"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 6dd247dd71a6..5568fa7d0b25 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ဘလူးတုသ်ချိတ်ဆက်ထားမှု"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ဘလူးတုသ်သုံးစက် သင်္ကေတ"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"စက်အသေးစိတ်ကို စီစဉ်သတ်မှတ်ရန် နှိပ်ပါ"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"စက်အားလုံးကြည့်ရန် နှိပ်ပါ"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"စက်အသစ် တွဲချိတ်ရန် နှိပ်ပါ"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ဘက်ထရီရာခိုင်နှုန်းကို မသိပါ။"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>သို့ ချိတ်ဆက်ထား"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> သို့ချိတ်ဆက်ထားပါသည်။"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ဘလူးတုသ်သုံးရန်"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ချိတ်ဆက်ထားသည်"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"သိမ်းထားသည်"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"စသုံးရန်"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ဘက်ထရီ"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"အသံ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"မိုက်ခွက်ပါနားကြပ်"</string> @@ -405,9 +401,10 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • နှေးကွေးစွာ အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"အများသုံးရှင်းလင်းပို့ချချက် စတင်ရန် ဘယ်သို့ပွတ်ဆွဲပါ"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> + <!-- no translation found for button_to_open_widget_editor (5599945944349057600) --> <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> + <string name="button_to_remove_widget" msgid="1511255853677835341">"ဝိဂျက် ဖယ်ရှားရန်"</string> + <!-- no translation found for hub_mode_add_widget_button_text (3956587989338301487) --> <skip /> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"အသုံးပြုသူကို ပြောင်းလဲရန်"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ဆွဲချမီနူး"</string> @@ -1217,8 +1214,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) က လတ်တလောသုံးထားသည်"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) က သုံးနေသည်"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) က လတ်တလောသုံးထားသည်"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"ကီးဘုတ်နောက်မီး"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"အဆင့် %2$d အနက် %1$d"</string> </resources> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index f873274d9f95..c654397a6fc3 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth er tilkoblet."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikon for Bluetooth-enheter"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klikk for å konfigurere enhetsdetaljer"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klikk for å se alle enhetene"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klikk for å koble sammen en ny enhet"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batteriprosenten er ukjent."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Koblet til <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Koblet til <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bruk Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tilkoblet"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Lagret"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koble fra"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiver"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Hodetelefoner"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader sakte • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Sveip til venstre for å starte fellesveiledningen"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Åpne modulvelgeren"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Åpne redigeringsverktøyet for moduler"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Fjern en modul"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Legg til en modul"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Bytt bruker"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullegardinmeny"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apper og data i denne økten blir slettet."</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 24d7ffe5372d..71fa4e56f9ed 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लुटुथ जडान भयो।"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ब्लुटुथ डिभाइस जनाउने आइकन"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"डिभाइसको विवरण कन्फिगर गर्न क्लिक गर्नुहोस्"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"सबै डिभाइसहरू हेर्न क्लिक गर्नुहोस्"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"नयाँ डिभाइसमा कनेक्ट गर्न क्लिक गर्नुहोस्"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ब्याट्रीमा कति प्रतिशत चार्ज छ भन्ने कुराको जानाकरी छैन।"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> मा जडित।"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> मा कनेक्ट गरियो।"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लुटुथ प्रयोग गर्नुहोस्"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट गरिएको छ"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेभ गरिएको छ"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट गर्नुहोस्"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"एक्टिभेट गर्नुहोस्"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ब्याट्री"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"अडियो"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • बिस्तारै चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा पूरै चार्ज हुन्छ"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा फुल चार्ज हुने छ"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"कम्युनल ट्युटोरियल सुरु गर्न बायाँतिर स्वाइप गर्नुहोस्"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"विजेट पिकर खोल्नुहोस्"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट एडिटर खोल्नुहोस्"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"कुनै विजेट हटाउनुहोस्"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"विजेट हाल्नुहोस्"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"प्रयोगकर्ता फेर्नुहोस्"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनु"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यो सत्रमा भएका सबै एपहरू र डेटा मेटाइने छ।"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 9a110a251aba..c8f6c583a50e 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-verbinding ingesteld."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icoon voor bluetooth-apparaat"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klik om de apparaatgegevens in te stellen"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klik om alle apparaten te bekijken"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klik om nieuw apparaat te koppelen"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batterijpercentage onbekend."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Verbonden met <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Verbonden met <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth gebruiken"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbonden"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Opgeslagen"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"loskoppelen"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activeren"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batterijniveau"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Langzaam opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe naar links om de communitytutorial te starten"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Open de widgetkiezer"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"De widget-editor openen"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Verwijder een widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Widget toevoegen"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Gebruiker wijzigen"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pull-downmenu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps en gegevens in deze sessie worden verwijderd."</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index a7726277eab9..c62519dad363 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ବ୍ଲୁଟୂଥ୍ ସଂଯୋଗ କରାଯାଇଛି।"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ବ୍ଲୁଟୁଥ ଡିଭାଇସ ଆଇକନ"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ଡିଭାଇସ ବିବରଣୀକୁ କନଫିଗର କରିବା ପାଇଁ କ୍ଲିକ କରନ୍ତୁ"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"ସମସ୍ତ ଡିଭାଇସ ଦେଖିବାକୁ କ୍ଲିକ କରନ୍ତୁ"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"ନୂଆ ଡିଭାଇସ ପେୟାର କରିବାକୁ କ୍ଲିକ କରନ୍ତୁ"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ବ୍ୟାଟେରୀ ଶତକଡ଼ା ଅଜଣା ଅଟେ।"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ସହ ସଂଯୁକ୍ତ"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ସହିତ ସଂଯୁକ୍ତ।"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ବ୍ଲୁଟୁଥ ବ୍ୟବହାର କରନ୍ତୁ"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"କନେକ୍ଟ କରାଯାଇଛି"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ସେଭ କରାଯାଇଛି"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ଚାଲୁ କରନ୍ତୁ"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ବ୍ୟାଟେରୀ"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ଅଡିଓ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ହେଡସେଟ୍"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଧୀରେ ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"କମ୍ୟୁନାଲ ଟ୍ୟୁଟୋରିଆଲ ଆରମ୍ଭ କରିବା ପାଇଁ ବାମକୁ ସ୍ୱାଇପ କରନ୍ତୁ"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"ୱିଜେଟ ପିକର ଖୋଲନ୍ତୁ"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ୱିଜେଟ ଏଡିଟର ଖୋଲନ୍ତୁ"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"ଏକ ୱିଜେଟକୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ୟୁଜର୍ ବଦଳାନ୍ତୁ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ପୁଲଡାଉନ ମେନୁ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ସେସନର ସମସ୍ତ ଆପ୍ ଓ ଡାଟା ଡିଲିଟ୍ ହୋଇଯିବ।"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index bb956b6bc0a1..3f75dfe6f3e4 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ਕਨੈਕਟ ਕੀਤੀ।"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ਬਲੂਟੁੱਥ ਡੀਵਾਈਸ ਦਾ ਪ੍ਰਤੀਕ"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"ਡੀਵਾਈਸ ਦੇ ਵੇਰਵੇ ਦਾ ਸੰਰੂਪਣ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"\'ਸਾਰੇ ਡੀਵਾਈਸ ਦੇਖੋ\' \'ਤੇ ਕਲਿੱਕ ਕਰੋ"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"\'ਨਵਾਂ ਡੀਵਾਈਸ ਜੋੜਾਬੱਧ ਕਰੋ\' \'ਤੇ ਕਲਿੱਕ ਕਰੋ"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ਬੈਟਰੀ ਪ੍ਰਤੀਸ਼ਤ ਅਗਿਆਤ ਹੈ।"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ।"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ।"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ਬਲੂਟੁੱਥ ਵਰਤੋ"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ਕਨੈਕਟ ਹੈ"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ਕਿਰਿਆਸ਼ੀਲ ਕਰੋ"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ਬੈਟਰੀ"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ਆਡੀਓ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ਹੈੱਡਸੈੱਟ"</string> @@ -371,7 +367,7 @@ <string name="zen_alarms_introduction" msgid="3987266042682300470">"ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਤੁਹਾਨੂੰ ਪਰੇਸ਼ਾਨ ਨਹੀਂ ਕਰਨਗੀਆਂ, ਸਿਵਾਏ ਅਲਾਰਮਾਂ ਦੀ ਸੂਰਤ ਵਿੱਚ। ਤੁਸੀਂ ਅਜੇ ਵੀ ਸੰਗੀਤ, ਵੀਡੀਓ ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ ਆਪਣੀ ਚੋਣ ਅਨੁਸਾਰ ਕੁਝ ਵੀ ਸੁਣ ਸਕਦੇ ਹੋ।"</string> <string name="zen_priority_customize_button" msgid="4119213187257195047">"ਵਿਉਂਤਬੱਧ ਕਰੋ"</string> <string name="zen_silence_introduction_voice" msgid="853573681302712348">"ਇਹ ਅਲਾਰਮ, ਸੰਗੀਤ, ਵੀਡੀਓ, ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ, ਸਾਰੀਆਂ ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਨੂੰ ਬਲਾਕ ਕਰਦਾ ਹੈ। ਤੁਸੀਂ ਅਜੇ ਵੀ ਫ਼ੋਨ ਕਾਲ ਕਰਨ ਦੇ ਯੋਗ ਹੋਵੋਂਗੇ।"</string> - <string name="zen_silence_introduction" msgid="6117517737057344014">"ਇਹ ਅਲਾਰਮ, ਸੰਗੀਤ, ਵੀਡੀਓਜ਼, ਅਤੇ ਗੇਮਸ ਸਮੇਤ, ਸਾਰੀਆਂ ਧੁਨੀਆਂ ਅਤੇ ਵਾਇਬ੍ਰੇਸ਼ਨ ਨੂੰ ਬਲੌਕ ਕਰਦਾ ਹੈ।"</string> + <string name="zen_silence_introduction" msgid="6117517737057344014">"ਇਹ ਅਲਾਰਮ, ਸੰਗੀਤ, ਵੀਡੀਓ ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ, ਸਾਰੀਆਂ ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਨੂੰ ਬਲੌਕ ਕਰਦਾ ਹੈ।"</string> <string name="notification_tap_again" msgid="4477318164947497249">"ਖੋਲ੍ਹਣ ਲਈ ਦੁਬਾਰਾ ਟੈਪ ਕਰੋ"</string> <string name="tap_again" msgid="1315420114387908655">"ਦੁਬਾਰਾ ਟੈਪ ਕਰੋ"</string> <string name="keyguard_unlock" msgid="8031975796351361601">"ਖੋਲ੍ਹਣ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਹੌਲੀ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ਭਾਈਚਾਰਕ ਟਿਊਟੋਰੀਅਲ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਖੱਬੇ ਪਾਸੇ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"ਵਿਜੇਟ ਚੋਣਕਾਰ ਨੂੰ ਖੋਲ੍ਹੋ"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ਵਿਜੇਟ ਸੰਪਾਦਕ ਖੋਲ੍ਹੋ"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"ਵਿਜੇਟ ਨੂੰ ਹਟਾਓ"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ਵਰਤੋਂਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ਪੁੱਲਡਾਊਨ ਮੀਨੂ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿਚਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟਾ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਏਗਾ।"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index a28329f74a14..a26a5b54443b 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth połączony."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona urządzenia Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknij, aby skonfigurować szczegóły urządzenia"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknij, aby zobaczyć wszystkie urządzenia"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknij, aby sparować nowe urządzenie"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Poziom naładowania baterii jest nieznany."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Połączono z <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Połączono z urządzeniem <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Użyj Bluetootha"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Połączone"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Zapisane"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"rozłącz"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktywuj"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> naładowania baterii"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Dźwięk"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Zestaw słuchawkowy"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wolne ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Aby uruchomić wspólny samouczek, przeciągnij palcem w lewo"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otwórz edytor widżetów"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Usuń widżet"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Dodaj widżet"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Przełącz użytkownika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Ostatnio używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Ostatnio używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Podświetlenie klawiatury"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Poziom %1$d z %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 0c0aafa6651e..4b560602fbfc 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ícone de dispositivo Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Clique para configurar os detalhes do dispositivo"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Clique para conferir todos os dispositivos"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Clique para parear o novo dispositivo"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentagem da bateria desconhecida."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize para a esquerda para iniciar o tutorial compartilhado"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Abrir o seletor de widgets"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Remover um widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Adicionar widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 0cfc7aaecc81..09c48cbc4a08 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ligado."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ícone de dispositivo Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Clique para configurar o detalhe do dispositivo"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Clique para ver todos os dispositivos"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Clique para sincronizar um novo dispositivo"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percentagem da bateria desconhecida."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ligado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Ligado a <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ligado"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desassociar"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ausc. c/ mic. integ."</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar lentamente • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize rapidamente para a esquerda para iniciar o tutorial coletivo"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Abrir seletor de widgets"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir editor de widgets"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Remover widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Adicionar widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mudar utilizador"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pendente"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as apps e dados desta sessão serão eliminados."</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 0c0aafa6651e..4b560602fbfc 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ícone de dispositivo Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Clique para configurar os detalhes do dispositivo"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Clique para conferir todos os dispositivos"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Clique para parear o novo dispositivo"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentagem da bateria desconhecida."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize para a esquerda para iniciar o tutorial compartilhado"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Abrir o seletor de widgets"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Remover um widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Adicionar widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 4df30fa5636b..aaf20f3dfec0 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Conectat prin Bluetooth."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Pictograma de dispozitiv Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Dă clic pentru a configura detaliile dispozitivului"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Dă clic pentru a vedea toate dispozitivele"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Dă clic pentru a asocia noul dispozitiv"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procentajul bateriei este necunoscut."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectat la <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"S-a stabilit conexiunea la <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Folosește Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectat"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvat"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deconectează"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activează"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivelul bateriei: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Căști"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă lent • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Glisează spre stânga pentru a începe tutorialul pentru comunitate"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Deschide editorul de widgeturi"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Elimină un widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Adaugă widgetul"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Folosit recent de <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Se folosește pentru <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Folosit recent de <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Iluminarea din spate a tastaturii"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivelul %1$d din %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index c29dc11b5f5b..3836a2935f64 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-соединение установлено."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Значок устройства Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Нажмите, чтобы изменить информацию об устройстве"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Нажмите, чтобы показать все устройства"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Нажмите, чтобы подключить устройство"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Уровень заряда батареи в процентах неизвестен."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>: подключено."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Подключено к: <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Использовать"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Подключено"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сохранено"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"отключить"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активировать"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудиоустройство"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Медленная зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Чтобы ознакомиться с руководством, проведите по экрану влево"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Открыть редактор виджетов"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Удалить виджет"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Добавить виджет"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Сменить пользователя."</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"раскрывающееся меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Недавно использовалось приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Сейчас используется приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Недавно использовалось приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Подсветка клавиатуры"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Уровень %1$d из %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 68bd1ce3413e..409379c3b26d 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"බ්ලූටූත් සම්බන්ධිතයි."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"බ්ලූටූත් උපාංග නිරූපකය"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"උපාංග විස්තර වින්යාස කිරීමට ක්ලික් කරන්න"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"සියලු උපාංග බැලීමට ක්ලික් කරන්න"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"නව උපාංගය යුගල කිරීමට ක්ලික් කරන්න"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"බැටරි ප්රතිශතය නොදනී."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> වෙත සම්බන්ධ කරන ලදි."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> වෙත සම්බන්ධ විය."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"බ්ලූටූත් භාවිතා කරන්න"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"සම්බන්ධිතයි"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"සුරැකිණි"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"විසන්ධි කරන්න"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"සක්රිය කරන්න"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"බැටරිය <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ශ්රව්ය"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"හෙඩ්සෙටය"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • සෙමින් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"පොදු නිබන්ධනය ආරම්භ කිරීමට වමට ස්වයිප් කරන්න"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"විජට් සංස්කාරකය විවෘත කරන්න"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"විජට්ටුවක් ඉවත් කරන්න"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"විජට්ටුව එක් කරන්න"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"පරිශීලක මාරුව"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"නිපතන මෙනුව"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> විසින් මෑතකදී භාවිත කරන ලදි (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> භාවිත කරයි (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> විසින් මෑතකදී භාවිත කරන ලදි (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"යතුරු පුවරු පසු ආලෝකය"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$dන් %1$d වැනි මට්ටම"</string> </resources> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index f916bf272ba3..b4bf8d3b9429 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth pripojené."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona zariadenia s rozhraním Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknutím nakonfigurujte podrobnosti o zariadení"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknutím zobrazíte všetky zariadenia"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknutím spárujete nové zariadenie"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percento batérie nie je známe."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Pripojené k zariadeniu <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Pripojené k zariadeniu <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Použiť Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Pripojené"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uložené"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojiť"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovať"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batéria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Náhlavná súprava"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa pomaly • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Potiahnutím doľava spustite komunitný návod"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvoriť editor miniaplikácií"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Odstrániť miniaplikáciu"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Pridať miniaplikáciu"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Prepnutie používateľa"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbaľovacia ponuka"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedávno využila aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Využíva <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedávno využila aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Podsvietenie klávesnice"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d. úroveň z %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 72de807d4c04..8c4bd73fe38b 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Povezava Bluetooth vzpostavljena."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona naprave Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliknite za konfiguriranje podrobnosti o napravi"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliknite za ogled vseh naprav"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliknite za seznanitev nove naprave"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Neznan odstotek napolnjenosti baterije."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezava vzpostavljena z: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Vzpostavljena povezava: <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Uporabi Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Shranjeno"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinitev povezave"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterija na <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvok"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalke z mikrofonom"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Počasno polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Povlecite levo, da zaženete vadnico za skupnost"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Odpiranje urejevalnika pripomočkov"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Odstranitev pripomočka"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Dodaj pripomoček"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Preklop med uporabniki"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"spustni meni"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedavno uporabljala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Uporablja aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedavno uporabljala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Osvetlitev tipkovnice"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Stopnja %1$d od %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 2c1f6ac50f66..5a1cd597823c 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Pajisja është lidhur me \"bluetooth\"."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Ikona e pajisjes me Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Kliko për të konfiguruar detajet e pajisjes"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Kliko për të shikuar të gjitha pajisjet"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Kliko për të çiftuar një pajisje të re"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Përqindja e baterisë e panjohur."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Lidhur me <xliff:g id="BLUETOOTH">%s</xliff:g>"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Është lidhur me <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Përdor Bluetooth-in"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Lidhur"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ruajtur"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"shkëput"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizo"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kufje me mikrofon"</string> @@ -405,9 +401,10 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet ngadalë • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Rrëshqit shpejt majtas për të filluar udhëzuesin e përbashkët"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> + <!-- no translation found for button_to_open_widget_editor (5599945944349057600) --> <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Hiq një miniaplikacion"</string> + <!-- no translation found for hub_mode_add_widget_button_text (3956587989338301487) --> <skip /> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Ndërro përdorues"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyja me tërheqje poshtë"</string> @@ -1217,8 +1214,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Përdorur së fundi nga <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Në përdorim nga <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Përdorur së fundi nga <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Drita e sfondit e tastierës"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Niveli: %1$d nga %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 428cc5f71d07..887f5c625f3e 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth је прикључен."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Икона Bluetooth уређаја"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Кликните да бисте конфигурисали детаље о уређају"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Кликните да бисте видели све уређаје"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Кликните да бисте упарили нов уређај"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Проценат напуњености батерије није познат."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Повезани сте са <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Повезани смо са уређајем <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Користи Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Повезано"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сачувано"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекините везу"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирајте"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Ниво батерије је <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалице"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Споро се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Пуни се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Превуците улево да бисте започели заједнички водич"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Отвори бирач виџета"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Отвори уређивач виџета"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Уклони виџет"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Додај виџет"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Замени корисника"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падајући мени"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 179ed6e77714..3f9e45c6bd0c 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ansluten."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Enhetsikon för Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Klicka för att konfigurera enhetsinformation"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Klicka för att se alla enheter"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Klicka för att parkoppla en ny enhet"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Okänd batterinivå."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ansluten till <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Ansluten till <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Använd Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ansluten"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sparad"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koppla från"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivera"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ljud"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas långsamt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Svep åt vänster för att börja med gruppguiden"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Öppna widgetredigeraren"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Ta bort en widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Lägg till widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Byt användare"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullgardinsmeny"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Användes nyligen av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Används av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Användes nyligen av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Bakgrundsbelysning för tangentbord"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivå %1$d av %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 36aa7d56b767..007f0d3ee039 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth imeunganishwa."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Aikoni ya Kifaa chenye Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Bofya ili uweke mipangilio ya maelezo ya kifaa"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Bofya ili uone vifaa vyote"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Bofya ili uoanishe kifaa kipya"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Asilimia ya betri haijulikani."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Imeunganishwa kwenye <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Imeunganishwa kwenye <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Tumia Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Imeunganishwa"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Imehifadhiwa"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ondoa"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"anza kutumia"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Chaji ya betri ni <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Sauti"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Vifaa vya sauti"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji polepole • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Telezesha kidole kushoto ili uanze mafunzo ya pamoja"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Fungua kiteua wijeti"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Fungua kihariri cha wijeti"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Ondoa wijeti"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Weka Wijeti"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Badili mtumiaji"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyu ya kuvuta chini"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 1f3264635589..e5cf3b2eeb1b 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"புளூடூத் இணைக்கப்பட்டது."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"புளூடூத் சாதன ஐகான்"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"சாதன விவரத்தை உள்ளமைக்க கிளிக் செய்யலாம்"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"அனைத்துச் சாதனங்களையும் பார்க்க கிளிக் செய்யவும்"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"புதிய சாதனத்தை இணைக்க கிளிக் செய்யவும்"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"பேட்டரி சதவீதம் தெரியவில்லை."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>க்கு இணைக்கப்பட்டது."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> உடன் இணைக்கப்பட்டுள்ளது."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"புளூடூத்தைப் பயன்படுத்துதல்"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"இணைக்கப்பட்டது"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"சேமிக்கப்பட்டது"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"இணைப்பு நீக்கும்"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"செயல்படுத்தும்"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> பேட்டரி"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ஆடியோ"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ஹெட்செட்"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • மெதுவாக சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுவதும் சார்ஜாகும்"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"சமூகப் பயிற்சியைத் தொடங்க இடதுபுறம் ஸ்வைப் செய்யுங்கள்"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"விட்ஜெட் எடிட்டரைத் திறக்கும்"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"விட்ஜெட்டை அகற்றும்"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"விட்ஜெட்டைச் சேர்"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"பயனரை மாற்று"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"கீழ் இழுக்கும் மெனு"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ஆப்ஸால் சமீபத்தில் பயன்படுத்தப்பட்டது"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ஆப்ஸால் பயன்படுத்தப்படுகிறது"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ஆப்ஸால் சமீபத்தில் பயன்படுத்தப்பட்டது"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"கீபோர்டு பேக்லைட்"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"நிலை, %2$d இல் %1$d"</string> </resources> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index e7ba58349d04..8ec3905e5756 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"బ్లూటూత్ కనెక్ట్ చేయబడింది."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"బ్లూటూత్ పరికర చిహ్నం"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"పరికర వివరాలను కాన్ఫిగర్ చేయడానికి క్లిక్ చేయండి"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"అన్ని పరికరాలను చూడటానికి క్లిక్ చేయండి"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"కొత్త పరికరాన్ని పెయిర్ చేయడానికి క్లిక్ చేయండి"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"బ్యాటరీ శాతం తెలియదు."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>కి కనెక్ట్ చేయబడింది."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>కి కనెక్ట్ చేయబడింది."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"బ్లూటూత్ వాడండి"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"కనెక్ట్ అయింది"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"సేవ్ చేయబడింది"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"డిస్కనెక్ట్ చేయండి"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"యాక్టివేట్ చేయండి"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> బ్యాటరీ"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ఆడియో"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"హెడ్సెట్"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • నెమ్మదిగా ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తి ఛార్జ్"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"కమ్యూనల్ ట్యుటోరియల్ను ప్రారంభించడానికి ఎడమ వైపునకు స్వైప్ చేయండి"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"విడ్జెట్ ఎడిటర్ను తెరవండి"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"విడ్జెట్ను తీసివేయండి"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"విడ్జెట్ను జోడించండి"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"వినియోగదారుని మార్చు"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"పుల్డౌన్ మెనూ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్లోని అన్ని యాప్లు మరియు డేటా తొలగించబడతాయి."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ద్వారా ఇటీవల వినియోగించబడింది"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ద్వారా వినియోగంలో ఉంది"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ద్వారా ఇటీవల ఉపయోగించబడింది"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"కీబోర్డ్ బ్యాక్లైట్"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$dలో %1$dవ స్థాయి"</string> </resources> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 1651e540f72a..2f132807b243 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"เชื่อมต่อบลูทูธแล้ว"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"ไอคอนอุปกรณ์บลูทูธ"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"คลิกเพื่อกำหนดค่ารายละเอียดอุปกรณ์"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"คลิกเพื่อดูอุปกรณ์ทั้งหมด"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"คลิกเพื่อจับคู่อุปกรณ์ใหม่"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"ไม่ทราบเปอร์เซ็นต์แบตเตอรี่"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"เชื่อมต่อกับ <xliff:g id="BLUETOOTH">%s</xliff:g> แล้ว"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"เชื่อมต่อกับ <xliff:g id="CAST">%s</xliff:g>"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"ใช้บลูทูธ"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"เชื่อมต่อแล้ว"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"บันทึกแล้ว"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ยกเลิกการเชื่อมต่อ"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"เปิดใช้งาน"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"เสียง"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ชุดหูฟัง"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างช้าๆ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ปัดไปทางซ้ายเพื่อเริ่มบทแนะนำส่วนกลาง"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"เปิดเครื่องมือเลือกวิดเจ็ต"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"เปิดเครื่องมือแก้ไขวิดเจ็ต"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"นำวิดเจ็ตออก"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"เพิ่มวิดเจ็ต"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"สลับผู้ใช้"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"เมนูแบบเลื่อนลง"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 085fdeed18ab..11c75c62c989 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Nakakonekta ang Bluetooth."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Icon ng Bluetooth device"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"I-click para i-configure ang detalye ng device"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"I-click para tingnan ang lahat ng device"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"I-click para magpares ng bagong device"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Hindi alam ang porsyento ng baterya."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Nakakonekta sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Nakakonekta sa <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gumamit ng Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Nakakonekta"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Na-save"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"idiskonekta"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"i-activate"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> na baterya"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabagal na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Mag-swipe pakaliwa para simulan ang communal na tutorial"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Buksan ang picker ng widget"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buksan ang editor ng widget"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Mag-alis ng widget"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Idagdag ang Widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Magpalit ng user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 820ccd17f4d4..3f578d26e581 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth bağlandı."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth cihaz simgesi"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Cihaz ayrıntılarını yapılandırmak için tıklayın"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Tüm cihazları görmek için tıklayın"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Yeni cihaz eşlemek için tıklayın"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pil yüzdesi bilinmiyor."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ile bağlı."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> bağlantısı kuruldu."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth\'u kullan"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Bağlandı"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Kaydedildi"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"bağlantıyı kes"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"etkinleştir"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Pil düzeyi <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ses"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Mikrofonlu kulaklık"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Yavaş şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ortak eğitimi başlatmak için sola kaydırın"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Widget düzenleyiciyi açın"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Widget kaldır"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Widget Ekle"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kullanıcı değiştirme"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"açılır menü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"En son <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) tarafından kullanıldı"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) tarafından kullanılıyor"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"En son <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) tarafından kullanıldı"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Klavye aydınlatması"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Seviye %1$d / %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index ea3d0b1ed597..90dc097919fa 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth під’єднано."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Значок пристрою з Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Натисніть, щоб змінити налаштування пристрою"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Натисніть, щоб переглянути всі пристрої"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Натисніть, щоб підключити новий пристрій"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Відсоток заряду акумулятора невідомий."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Підключено до <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Під’єднано до пристрою <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Увімкнути Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Підключено"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Збережено"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"від’єднати"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активувати"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> заряду акумулятора"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудіопристрій"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string> @@ -405,9 +401,10 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Повільне заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Проведіть пальцем уліво, щоб відкрити спільний навчальний посібник"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> + <!-- no translation found for button_to_open_widget_editor (5599945944349057600) --> <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Вилучити віджет"</string> + <!-- no translation found for hub_mode_add_widget_button_text (3956587989338301487) --> <skip /> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Змінити користувача"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"спадне меню"</string> @@ -1217,8 +1214,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Нещодавно використано (<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Використовується додатком <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Нещодавно використано (<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Підсвічування клавіатури"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Рівень %1$d з %2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index bf58d9c28fd4..137c8ab7424b 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوٹوتھ مربوط ہے۔"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"بلوٹوتھ آلے کا آئیکن"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"آلہ کی تفصیل کو کنفیگر کرنے کے لیے کلک کریں"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"تمام آلات دیکھنے کے لیے کلک کریں"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"نئے آلے کا جوڑا بنانے کے لیے کلک کریں"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"بیٹری کی فیصد نامعلوم ہے۔"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> سے منسلک ہیں۔"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> سے منسلک ہے۔"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"استعمال کریں"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"منسلک ہے"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"محفوظ ہے"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"غیر منسلک کریں"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کریں"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> بیٹری"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"آڈیو"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ہیڈ سیٹ"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • آہستہ چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"کمیونل ٹیوٹوریل شروع کرنے کے لیے بائیں سوائپ کریں"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"ویجیٹ چنندہ کو کھولیں"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ویجیٹ ایڈیٹر کو کھولیں"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"ویجیٹ ہٹائیں"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"ویجٹ شامل کریں"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"صارف سوئچ کریں"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"پل ڈاؤن مینیو"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 0e04cd5fd191..601d773bc0ce 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ulandi."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Bluetooth qurilma belgisi"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Qurilma haqida tafsilotlarni oʻzgartirish uchun bosing"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Barcha qurilmalarni koʻrish uchun bosing"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Yangi qurilmani ulash uchun bosing"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batareya quvvati foizi nomaʼlum."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ulangan: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Bunga ulangan: <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ishlatish"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ulandi"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saqlangan"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"uzish"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"faollashtirish"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batareya quvvati: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Garnitura"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sekin quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Qoʻllanma bilan tanishish uchun chapga suring"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"Vidjet tanlash vositasini ochish"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidjet muharririni ochish"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"Vidjetni olib tashlash"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Vidjet"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Foydalanuvchini almashtirish"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"tortib tushiriladigan menyu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 31350be4091b..b67c789342d9 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Đã kết nối bluetooth."</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Biểu tượng thiết bị Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Nhấp để định cấu hình thông tin thiết bị"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Nhấp để xem tất cả các thiết bị"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Nhấp để ghép nối thiết bị mới"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Tỷ lệ phần trăm pin không xác định."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Đã kết nối với <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Đã kết nối với <xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bật Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Đã kết nối"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Đã lưu"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ngắt kết nối"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"kích hoạt"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> pin"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Âm thanh"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Tai nghe"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc chậm • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Vuốt sang trái để bắt đầu xem hướng dẫn chung"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Mở trình chỉnh sửa tiện ích"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Xoá tiện ích"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Thêm tiện ích"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Chuyển đổi người dùng"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"trình đơn kéo xuống"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) đã dùng gần đây"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) đang dùng"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) đã dùng gần đây"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Đèn nền bàn phím"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Độ sáng %1$d/%2$d"</string> </resources> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 4db1a91b1ba7..576cbc745e03 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"蓝牙已连接。"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"蓝牙设备图标"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"点击以配置设备详情"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"点击即可查看所有设备"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"点击即可配对新设备"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"电池电量百分比未知。"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已连接到<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"已连接到 <xliff:g id="CAST">%s</xliff:g>。"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"使用蓝牙"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已连接"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已保存"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"断开连接"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"启用"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> 的电量"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音频"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳机"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在慢速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑动即可启动公共教程"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"打开微件选择器"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"打开微件编辑器"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"移除微件"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"添加微件"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切换用户"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉菜单"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 125cfe15164a..bcf7478d191c 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"藍牙裝置圖示"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"按一下即可設定裝置詳情"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"㩒一下就可以睇所有裝置"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"㩒一下就可以配對新裝置"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"電量百分比不明。"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"已連接至 <xliff:g id="CAST">%s</xliff:g>。"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"使用藍牙"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已連接"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已儲存"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"解除連結"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟動"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string> @@ -405,8 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑動即可開始共用教學課程"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"開啟小工具挑選器"</string> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"開啟小工具編輯器"</string> <string name="button_to_remove_widget" msgid="1511255853677835341">"移除小工具"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"新增小工具"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index dba68c500dc0..76d11ece8eee 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"「藍牙裝置」圖示"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"按一下即可設定裝置詳細資料"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"按一下即可查看所有裝置"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"按一下即可配對新裝置"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"電池電量不明。"</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"已連線至 <xliff:g id="CAST">%s</xliff:g>。"</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"使用藍牙"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已連線"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已儲存"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"取消連結"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟用"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string> @@ -405,8 +401,11 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑動即可啟動通用教學課程"</string> - <string name="button_to_open_widget_picker" msgid="8007261659745030810">"開啟小工具挑選器"</string> + <!-- no translation found for button_to_open_widget_editor (5599945944349057600) --> + <skip /> <string name="button_to_remove_widget" msgid="1511255853677835341">"移除小工具"</string> + <!-- no translation found for hub_mode_add_widget_button_text (3956587989338301487) --> + <skip /> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會遭到刪除。"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 982ca4a12dd8..591a63e3f37f 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -197,10 +197,8 @@ <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ixhunyiwe"</string> <string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"Isithonjana sedivayisi ye-Bluetooth"</string> <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Chofoza ukuze ulungiselele imininingwane yedivayisi"</string> - <!-- no translation found for accessibility_bluetooth_device_settings_see_all (9111952496905423543) --> - <skip /> - <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (2435184865793496966) --> - <skip /> + <string name="accessibility_bluetooth_device_settings_see_all" msgid="9111952496905423543">"Chofoza ukuze ubone wonke amadivayisi"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="2435184865793496966">"Chofoza ukuze ubhangqe idivayisi entsha"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Iphesenti lebhethri alaziwa."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Xhuma ku-<xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Ixhumeke ku-<xliff:g id="CAST">%s</xliff:g>."</string> @@ -259,10 +257,8 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Sebenzisa i-Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ixhunyiwe"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ilondoloziwe"</string> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_disconnect (415980329093277342) --> - <skip /> - <!-- no translation found for accessibility_quick_settings_bluetooth_device_tap_to_activate (3724301751036877403) --> - <skip /> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"nqamula"</string> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"yenza kusebenze"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ibhethri"</string> <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Umsindo"</string> <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ihedisethi"</string> @@ -405,10 +401,9 @@ <string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ishaja kancane • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Iyashaja • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string> <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swayiphela kwesokunxele ukuze uqale okokufundisa komphakathi"</string> - <!-- no translation found for button_to_open_widget_picker (8007261659745030810) --> - <skip /> - <!-- no translation found for button_to_remove_widget (1511255853677835341) --> - <skip /> + <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vula isihleli sewijethi"</string> + <string name="button_to_remove_widget" msgid="1511255853677835341">"Susa iwijethi"</string> + <string name="hub_mode_add_widget_button_text" msgid="3956587989338301487">"Engeza Iwijethi"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Shintsha umsebenzisi"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"imenyu yokudonsela phansi"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wonke ama-app nedatha kulesi sikhathi azosuswa."</string> @@ -1217,8 +1212,6 @@ <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Kusetshenziswe kamuva yi-<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string> <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Isetshenziswa yi-<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Kusetshenziswe kamuva yi-<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> - <!-- no translation found for keyboard_backlight_dialog_title (8273102932345564724) --> - <skip /> - <!-- no translation found for keyboard_backlight_value (7336398765584393538) --> - <skip /> + <string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Ilambu lekhibhodi"</string> + <string name="keyboard_backlight_value" msgid="7336398765584393538">"Ileveli %1$d ka-%2$d"</string> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 0620355c2f98..e124aa0eda37 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -119,9 +119,8 @@ <!-- Keyboard shortcuts colors --> <color name="ksh_application_group_color">#fff44336</color> - <color name="ksh_key_item_color">@color/material_grey_600</color> - <color name="ksh_key_item_background">@color/material_grey_100</color> - <color name="ksh_key_item_new_background">@color/transparent</color> + <color name="ksh_key_item_color">?androidprv:attr/materialColorOnSurfaceVariant</color> + <color name="ksh_key_item_background">?androidprv:attr/materialColorSurfaceContainerHighest</color> <color name="instant_apps_color">#ff4d5a64</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 30392d2c9d85..03960d522653 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -940,8 +940,11 @@ <!-- Keyboard shortcuts helper --> <dimen name="ksh_layout_width">@dimen/match_parent</dimen> <dimen name="ksh_item_text_size">14sp</dimen> - <dimen name="ksh_item_padding">4dp</dimen> + <dimen name="ksh_item_padding">0dp</dimen> <dimen name="ksh_item_margin_start">4dp</dimen> + <dimen name="ksh_icon_scaled_size">18dp</dimen> + <dimen name="ksh_key_view_padding_vertical">4dp</dimen> + <dimen name="ksh_key_view_padding_horizontal">12dp</dimen> <!-- The size of corner radius of the arrow in the onboarding toast. --> <dimen name="recents_onboarding_toast_arrow_corner_radius">2dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index daf6cb3d683d..f49d2a19bcd4 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1773,13 +1773,13 @@ <!-- Name used to refer to the "Back" key on the keyboard. --> <string name="keyboard_key_back">Back</string> <!-- Name used to refer to the "Up" arrow key on the keyboard. --> - <string name="keyboard_key_dpad_up">Up</string> + <string name="keyboard_key_dpad_up">Up arrow</string> <!-- Name used to refer to the "Down" arrow key on the keyboard. --> - <string name="keyboard_key_dpad_down">Down</string> + <string name="keyboard_key_dpad_down">Down arrow</string> <!-- Name used to refer to the "Left" arrow key on the keyboard. --> - <string name="keyboard_key_dpad_left">Left</string> + <string name="keyboard_key_dpad_left">Left arrow</string> <!-- Name used to refer to the "Right" arrow key on the keyboard. --> - <string name="keyboard_key_dpad_right">Right</string> + <string name="keyboard_key_dpad_right">Right arrow</string> <!-- Name used to refer to the "Center" arrow key on the keyboard. --> <string name="keyboard_key_dpad_center">Center</string> <!-- Name used to refer to the "Tab" key on the keyboard. --> @@ -1834,9 +1834,11 @@ <string name="keyboard_shortcut_group_system_shortcuts_helper">Keyboard Shortcuts</string> <!-- User visible title for the keyboard shortcut that switches to the next hardware keyboard layout. --> <string name="keyboard_shortcut_group_system_switch_input">Switch keyboard layout</string> + <!-- User visible string that joins different shortcuts in a list, e.g. shortcut1 "or" shortcut2 "or" ... --> + <string name="keyboard_shortcut_join">or</string> - <!-- Content description for the clear text button in shortcut search list. [CHAR LIMIT=NONE] --> - <string name="keyboard_shortcut_clear_text">Clear text</string> + <!-- Content description for the clear search button in shortcut search list. [CHAR LIMIT=NONE] --> + <string name="keyboard_shortcut_clear_text">Clear search query</string> <!-- The title for keyboard shortcut search list [CHAR LIMIT=25] --> <string name="keyboard_shortcut_search_list_title">Shortcuts</string> <!-- The hint for keyboard shortcut search list [CHAR LIMIT=25] --> @@ -1852,52 +1854,63 @@ <!-- The title of current app category in shortcut search list. [CHAR LIMIT=25] --> <string name="keyboard_shortcut_search_category_current_app">Current app</string> + <!-- A11y message when showing keyboard shortcut search results. [CHAR LIMT=NONE] --> + <string name="keyboard_shortcut_a11y_show_search_results">Showing search results</string> + <!-- A11y message when filtering to "system" keyboard shortcuts. [CHAR LIMT=NONE] --> + <string name="keyboard_shortcut_a11y_filter_system">Showing system shortcuts</string> + <!-- A11y message when filtering to "input" keyboard shortcuts. [CHAR LIMT=NONE] --> + <string name="keyboard_shortcut_a11y_filter_input">Showing input shortcuts</string> + <!-- A11y message when filtering to "app opening" keyboard shortcuts. [CHAR LIMT=NONE] --> + <string name="keyboard_shortcut_a11y_filter_open_apps">Showing shortcuts that open apps</string> + <!-- A11y message when filtering to "current app" keyboard shortcuts. [CHAR LIMT=NONE] --> + <string name="keyboard_shortcut_a11y_filter_current_app">Showing shortcuts for the current app</string> + <!-- User visible title for the keyboard shortcut that triggers the notification shade. [CHAR LIMIT=70] --> - <string name="group_system_access_notification_shade">Access notification shade</string> + <string name="group_system_access_notification_shade">View notifications</string> <!-- User visible title for the keyboard shortcut that takes a full screenshot. [CHAR LIMIT=70] --> - <string name="group_system_full_screenshot">Take a full screenshot</string> + <string name="group_system_full_screenshot">Take screenshot</string> <!-- User visible title for the keyboard shortcut that access list of system / apps shortcuts. [CHAR LIMIT=70] --> - <string name="group_system_access_system_app_shortcuts">Access list of system / apps shortcuts</string> + <string name="group_system_access_system_app_shortcuts">Show shortcuts</string> <!-- User visible title for the keyboard shortcut that goes back to previous state. [CHAR LIMIT=70] --> - <string name="group_system_go_back">Back: go back to previous state (back button)</string> + <string name="group_system_go_back">Go back</string> <!-- User visible title for the keyboard shortcut that takes the user to the home screen. [CHAR LIMIT=70] --> - <string name="group_system_access_home_screen">Access home screen</string> + <string name="group_system_access_home_screen">Go to home screen</string> <!-- User visible title for the keyboard shortcut that triggers overview of open apps. [CHAR LIMIT=70] --> - <string name="group_system_overview_open_apps">Overview of open apps</string> + <string name="group_system_overview_open_apps">View recent apps</string> <!-- User visible title for the keyboard shortcut that cycles through recent apps (forward). [CHAR LIMIT=70] --> - <string name="group_system_cycle_forward">Cycle through recent apps (forward)</string> + <string name="group_system_cycle_forward">Cycle forward through recent apps</string> <!-- User visible title for the keyboard shortcut that cycles through recent apps (back). [CHAR LIMIT=70] --> - <string name="group_system_cycle_back">Cycle through recent apps (back)</string> + <string name="group_system_cycle_back">Cycle backward through recent apps</string> <!-- User visible title for the keyboard shortcut that accesses list of all apps and search. [CHAR LIMIT=70] --> - <string name="group_system_access_all_apps_search">Access list of all apps and search (i.e. Search/Launcher)</string> + <string name="group_system_access_all_apps_search">Open apps list</string> <!-- User visible title for the keyboard shortcut that hides and (re)showes taskbar. [CHAR LIMIT=70] --> - <string name="group_system_hide_reshow_taskbar">Hide and (re)show taskbar</string> - <!-- User visible title for the keyboard shortcut that accesses system settings. [CHAR LIMIT=70] --> - <string name="group_system_access_system_settings">Access system settings</string> - <!-- User visible title for the keyboard shortcut that accesses Google Assistant. [CHAR LIMIT=70] --> - <string name="group_system_access_google_assistant">Access Google Assistant</string> + <string name="group_system_hide_reshow_taskbar">Show taskbar</string> + <!-- User visible title for the keyboard shortcut that accesses [system] settings. [CHAR LIMIT=70] --> + <string name="group_system_access_system_settings">Open settings</string> + <!-- User visible title for the keyboard shortcut that accesses Assistant app. [CHAR LIMIT=70] --> + <string name="group_system_access_google_assistant">Open assistant</string> <!-- User visible title for the keyboard shortcut that locks screen. [CHAR LIMIT=70] --> <string name="group_system_lock_screen">Lock screen</string> <!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] --> - <string name="group_system_quick_memo">Pull up Notes app for quick memo</string> + <string name="group_system_quick_memo">Open notes</string> <!-- User visible title for the system multitasking keyboard shortcuts list. [CHAR LIMIT=70] --> <string name="keyboard_shortcut_group_system_multitasking">System multitasking</string> <!-- User visible title for the keyboard shortcut that enters split screen with current app to RHS [CHAR LIMIT=70] --> - <string name="system_multitasking_rhs">Enter Split screen with current app to RHS</string> + <string name="system_multitasking_rhs">Enter split screen with current app to RHS</string> <!-- User visible title for the keyboard shortcut that enters split screen with current app to LHS [CHAR LIMIT=70] --> - <string name="system_multitasking_lhs">Enter Split screen with current app to LHS</string> + <string name="system_multitasking_lhs">Enter split screen with current app to LHS</string> <!-- User visible title for the keyboard shortcut that switches from split screen to full screen [CHAR LIMIT=70] --> - <string name="system_multitasking_full_screen">Switch from Split screen to full screen</string> + <string name="system_multitasking_full_screen">Switch from split screen to full screen</string> <!-- User visible title for the keyboard shortcut that replaces an app from one to another during split screen [CHAR LIMIT=70] --> - <string name="system_multitasking_replace">During Split screen: replace an app from one to another</string> + <string name="system_multitasking_replace">During split screen: replace an app from one to another</string> <!-- User visible title for the input keyboard shortcuts list. [CHAR LIMIT=70] --> <string name="keyboard_shortcut_group_input">Input</string> <!-- User visible title for the keyboard shortcut that switches input language (next language). [CHAR LIMIT=70] --> - <string name="input_switch_input_language_next">Switch input language (next language)</string> + <string name="input_switch_input_language_next">Switch to next language</string> <!-- User visible title for the keyboard shortcut that switches input language (previous language). [CHAR LIMIT=70] --> - <string name="input_switch_input_language_previous">Switch input language (previous language)</string> + <string name="input_switch_input_language_previous">Switch to previous language</string> <!-- User visible title for the keyboard shortcut that accesses emoji. [CHAR LIMIT=70] --> <string name="input_access_emoji">Access emoji</string> <!-- User visible title for the keyboard shortcut that accesses voice typing. [CHAR LIMIT=70] --> @@ -1905,14 +1918,14 @@ <!-- User visible title for the system-wide applications keyboard shortcuts list. [CHAR LIMIT=70] --> <string name="keyboard_shortcut_group_applications">Applications</string> - <!-- User visible title for the keyboard shortcut that takes the user to the assist app. [CHAR LIMIT=70] --> - <string name="keyboard_shortcut_group_applications_assist">Assist</string> + <!-- User visible title for the keyboard shortcut that takes the user to the assistant app. [CHAR LIMIT=70] --> + <string name="keyboard_shortcut_group_applications_assist">Assistant</string> <!-- User visible title for the keyboard shortcut that takes the user to the browser app. [CHAR LIMIT=70] --> - <string name="keyboard_shortcut_group_applications_browser">Browser (Chrome as default)</string> + <string name="keyboard_shortcut_group_applications_browser">Browser</string> <!-- User visible title for the keyboard shortcut that takes the user to the contacts app. [CHAR LIMIT=70] --> <string name="keyboard_shortcut_group_applications_contacts">Contacts</string> <!-- User visible title for the keyboard shortcut that takes the user to the email app. [CHAR LIMIT=70] --> - <string name="keyboard_shortcut_group_applications_email">Email (Gmail as default)</string> + <string name="keyboard_shortcut_group_applications_email">Email</string> <!-- User visible title for the keyboard shortcut that takes the user to the SMS messaging app. [CHAR LIMIT=70] --> <string name="keyboard_shortcut_group_applications_sms">SMS</string> <!-- User visible title for the keyboard shortcut that takes the user to the music app. [CHAR LIMIT=70] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index befee2b3eeb7..c48dd9d2f68b 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -420,6 +420,11 @@ <!-- Cannot double inherit. Use Theme.SystemUI.QuickSettings in code to match --> <style name="BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog"> <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:windowAnimationStyle">@style/Animation.BrightnessDialog</item> + </style> + + <style name="Animation.BrightnessDialog"> + <item name="android:windowExitAnimation">@anim/instant_fade_out</item> </style> <style name="Theme.SystemUI.ContrastDialog" parent="@android:style/Theme.DeviceDefault.Dialog"> @@ -1513,10 +1518,6 @@ <item name="android:background">?android:attr/dividerHorizontal</item> </style> - <style name="ShortcutItemBackground"> - <item name="android:background">@color/ksh_key_item_new_background</item> - </style> - <style name="LongPressLockScreenAnimation"> <item name="android:windowEnterAnimation">@anim/long_press_lock_screen_popup_enter</item> <item name="android:windowExitAnimation">@anim/long_press_lock_screen_popup_exit</item> diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt index c9e57b45612c..b33f6fa7eadb 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt @@ -32,8 +32,7 @@ constructor(private val activityManager: ActivityManager) : CurrentActivityTypeP override val isHomeActivity: Boolean? get() = _isHomeActivity - private var _isHomeActivity: Boolean? = null - + @Volatile private var _isHomeActivity: Boolean? = null override fun init() { _isHomeActivity = activityManager.isOnHomeActivity() diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt index 3b8d318a3a79..baa88897947c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt @@ -18,18 +18,19 @@ import android.content.Context import android.hardware.devicestate.DeviceStateManager import com.android.systemui.unfold.updates.FoldProvider import com.android.systemui.unfold.updates.FoldProvider.FoldCallback +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton @Singleton -class DeviceStateManagerFoldProvider @Inject constructor( - private val deviceStateManager: DeviceStateManager, - private val context: Context -) : FoldProvider { +class DeviceStateManagerFoldProvider +@Inject +constructor(private val deviceStateManager: DeviceStateManager, private val context: Context) : + FoldProvider { - private val callbacks: MutableMap<FoldCallback, - DeviceStateManager.DeviceStateCallback> = hashMapOf() + private val callbacks = + ConcurrentHashMap<FoldCallback, DeviceStateManager.DeviceStateCallback>() override fun registerCallback(callback: FoldCallback, executor: Executor) { val listener = FoldStateListener(context, callback) @@ -39,13 +40,9 @@ class DeviceStateManagerFoldProvider @Inject constructor( override fun unregisterCallback(callback: FoldCallback) { val listener = callbacks.remove(callback) - listener?.let { - deviceStateManager.unregisterCallback(it) - } + listener?.let { deviceStateManager.unregisterCallback(it) } } - private inner class FoldStateListener( - context: Context, - listener: FoldCallback - ) : DeviceStateManager.FoldStateListener(context, { listener.onFoldUpdated(it) }) + private inner class FoldStateListener(context: Context, listener: FoldCallback) : + DeviceStateManager.FoldStateListener(context, { listener.onFoldUpdated(it) }) } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt index 7b67e3f3c920..7af991743457 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt @@ -15,24 +15,29 @@ package com.android.systemui.unfold.system import android.os.Handler +import android.os.HandlerThread +import android.os.Looper +import android.os.Process import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig -import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg +import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.unfold.dagger.UnfoldMain +import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg import com.android.systemui.unfold.updates.FoldProvider import com.android.systemui.unfold.util.CurrentActivityTypeProvider import dagger.Binds import dagger.Module +import dagger.Provides import java.util.concurrent.Executor +import javax.inject.Singleton /** - * Dagger module with system-only dependencies for the unfold animation. - * The code that is used to calculate unfold transition progress - * depends on some hidden APIs that are not available in normal - * apps. In order to re-use this code and use alternative implementations - * of these classes in other apps and hidden APIs here. + * Dagger module with system-only dependencies for the unfold animation. The code that is used to + * calculate unfold transition progress depends on some hidden APIs that are not available in normal + * apps. In order to re-use this code and use alternative implementations of these classes in other + * apps and hidden APIs here. */ @Module abstract class SystemUnfoldSharedModule { @@ -61,4 +66,22 @@ abstract class SystemUnfoldSharedModule { @Binds @UnfoldSingleThreadBg abstract fun backgroundExecutor(@UiBackground executor: Executor): Executor + + companion object { + @Provides + @UnfoldBg + @Singleton + fun unfoldBgProgressHandler(@UnfoldBg looper: Looper): Handler { + return Handler(looper) + } + + @Provides + @UnfoldBg + @Singleton + fun provideBgLooper(): Looper { + return HandlerThread("UnfoldBg", Process.THREAD_PRIORITY_FOREGROUND) + .apply { start() } + .looper + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 85a119c389c1..72d14ba543e2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -102,8 +102,6 @@ import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; import com.android.systemui.util.time.SystemClock; -import kotlin.Unit; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -113,6 +111,8 @@ import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Provider; +import kotlin.Unit; + import kotlinx.coroutines.ExperimentalCoroutinesApi; /** @@ -588,7 +588,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { // Always pilfer pointers that are within sensor area or when alternate bouncer is showing if (mActivePointerId != MotionEvent.INVALID_POINTER_ID - || mAlternateBouncerInteractor.isVisibleState()) { + || (mAlternateBouncerInteractor.isVisibleState() + && !DeviceEntryUdfpsRefactor.isEnabled())) { shouldPilfer = true; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt index 050b399fd3e8..ff23837703b5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt @@ -30,13 +30,16 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import java.util.concurrent.Executor import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.stateIn /** Repository for the current state of the display */ @@ -66,7 +69,8 @@ constructor( deviceStateManager: DeviceStateManager, displayManager: DisplayManager, @Main handler: Handler, - @Main mainExecutor: Executor + @Background backgroundExecutor: Executor, + @Background backgroundDispatcher: CoroutineDispatcher, ) : DisplayStateRepository { override val isReverseDefaultRotation = context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation) @@ -94,9 +98,10 @@ constructor( } sendRearDisplayStateUpdate(false) - deviceStateManager.registerCallback(mainExecutor, callback) + deviceStateManager.registerCallback(backgroundExecutor, callback) awaitClose { deviceStateManager.unregisterCallback(callback) } } + .flowOn(backgroundDispatcher) .stateIn( applicationScope, started = SharingStarted.Eagerly, @@ -137,6 +142,7 @@ constructor( ) awaitClose { displayManager.unregisterDisplayListener(callback) } } + .flowOn(backgroundDispatcher) .stateIn( applicationScope, started = SharingStarted.Eagerly, diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java index 334cf9318322..740e8bb5ff08 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java @@ -45,6 +45,7 @@ public abstract class Classifier { public static final int BACK_GESTURE = 16; public static final int QS_SWIPE_NESTED = 17; public static final int MEDIA_SEEKBAR = 18; + public static final int ALTERNATE_BOUNCER_SWIPE = 19; @IntDef({ QUICK_SETTINGS, @@ -65,6 +66,7 @@ public abstract class Classifier { QS_SWIPE_NESTED, BACK_GESTURE, MEDIA_SEEKBAR, + ALTERNATE_BOUNCER_SWIPE, }) @Retention(RetentionPolicy.SOURCE) public @interface InteractionType {} diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java index 15e2e9a916b9..b13bf4e051d2 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java @@ -22,6 +22,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHT import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VELOCITY_TO_DISTANCE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN; +import static com.android.systemui.classifier.Classifier.ALTERNATE_BOUNCER_SWIPE; import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; @@ -159,7 +160,8 @@ class DistanceClassifier extends FalsingClassifier { || interactionType == QS_COLLAPSE || interactionType == Classifier.UDFPS_AUTHENTICATION || interactionType == Classifier.QS_SWIPE_SIDE - || interactionType == QS_SWIPE_NESTED) { + || interactionType == QS_SWIPE_NESTED + || interactionType == ALTERNATE_BOUNCER_SWIPE) { return Result.passed(0); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java index 2fb6aaf2ec65..93aa27959aae 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java @@ -17,6 +17,7 @@ package com.android.systemui.classifier; +import static com.android.systemui.classifier.Classifier.ALTERNATE_BOUNCER_SWIPE; import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE; @@ -73,6 +74,7 @@ public class TypeClassifier extends FalsingClassifier { case NOTIFICATION_DISMISS: wrongDirection = vertical; break; + case ALTERNATE_BOUNCER_SWIPE: case UNLOCK: case BOUNCER_UNLOCK: wrongDirection = !vertical || !up; diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt index 48d3fe000ff8..fdd98bec0a2d 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/SharedNotificationContainerPosition.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt @@ -16,13 +16,15 @@ package com.android.systemui.common.shared.model -/** Positioning info for the shared notification container */ -data class SharedNotificationContainerPosition( +/** Models the bounds of the notification container. */ +data class NotificationContainerBounds( + /** The position of the top of the container in its window coordinate system, in pixels. */ val top: Float = 0f, + /** The position of the bottom of the container in its window coordinate system, in pixels. */ val bottom: Float = 0f, - - /** Whether any modifications to top/bottom are smoothly animated */ - val animate: Boolean = false, + /** Whether any modifications to top/bottom should be smoothly animated. */ + val isAnimated: Boolean = false, ) { + /** The current height of the notification container. */ val height: Float = bottom - top } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt index 7bca86e2e8fb..12be32c54b22 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt @@ -31,8 +31,10 @@ import com.android.systemui.statusbar.policy.onThemeChanged import com.android.systemui.util.kotlin.emitOnStart import com.android.systemui.util.view.bindLatest import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -95,7 +97,8 @@ constructor( * call [onInflate] on the resulting view each time. Disposes of the [DisposableHandle] returned by * [onInflate] when done. * - * This never completes unless cancelled, it just suspends and waits for updates. + * This never completes unless cancelled, it just suspends and waits for updates. It runs on a + * background thread using [backgroundDispatcher]. * * For parameters [resource], [root] and [attachToRoot], see [LayoutInflater.inflate]. * @@ -105,7 +108,7 @@ constructor( * ``` * parentView.repeatWhenAttached { * configurationState - * .reinflateOnChange( + * .reinflateAndBindLatest( * R.layout.my_layout, * parentView, * attachToRoot = false, @@ -124,7 +127,10 @@ suspend fun <T : View> ConfigurationState.reinflateAndBindLatest( @LayoutRes resource: Int, root: ViewGroup?, attachToRoot: Boolean, + backgroundDispatcher: CoroutineDispatcher, onInflate: (T) -> DisposableHandle?, ) { - inflateLayout<T>(resource, root, attachToRoot).bindLatest(onInflate) + inflateLayout<T>(resource, root, attachToRoot) + .flowOn(backgroundDispatcher) + .bindLatest(onInflate) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index dcacd090fa27..e7b87730f94b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -19,6 +19,7 @@ package com.android.systemui.dagger; import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; +import com.android.systemui.Flags; import com.android.systemui.InitController; import com.android.systemui.SystemUIAppComponentFactoryBase; import com.android.systemui.dagger.qualifiers.PerUser; @@ -34,10 +35,9 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.unfold.FoldStateLogger; import com.android.systemui.unfold.FoldStateLoggingProvider; import com.android.systemui.unfold.SysUIUnfoldComponent; -import com.android.systemui.unfold.UnfoldLatencyTracker; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; +import com.android.systemui.unfold.dagger.UnfoldBg; import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder; -import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.desktopmode.DesktopMode; @@ -62,7 +62,7 @@ import javax.inject.Provider; /** * An example Dagger Subcomponent for Core SysUI. - * + * <p> * See {@link ReferenceSysUIComponent} for the one actually used by AOSP. */ @SysUISingleton @@ -131,23 +131,34 @@ public interface SysUIComponent { default void init() { // Initialize components that have no direct tie to the dagger dependency graph, // but are critical to this component's operation - getSysUIUnfoldComponent().ifPresent(c -> { - c.getUnfoldLightRevealOverlayAnimation().init(); - c.getUnfoldTransitionWallpaperController().init(); - c.getUnfoldHapticsPlayer(); - }); - getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init()); + getSysUIUnfoldComponent() + .ifPresent( + c -> { + c.getUnfoldLightRevealOverlayAnimation().init(); + c.getUnfoldTransitionWallpaperController().init(); + c.getUnfoldHapticsPlayer(); + c.getNaturalRotationUnfoldProgressProvider().init(); + c.getUnfoldLatencyTracker().init(); + }); // No init method needed, just needs to be gotten so that it's created. getMediaMuteAwaitConnectionCli(); getNearbyMediaDevicesManager(); - getUnfoldLatencyTracker().init(); getConnectingDisplayViewModel().init(); getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init); getFoldStateLogger().ifPresent(FoldStateLogger::init); - getUnfoldTransitionProgressProvider().ifPresent((progressProvider) -> - getUnfoldTransitionProgressForwarder().ifPresent((forwarder) -> - progressProvider.addCallback(forwarder) - )); + + Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider; + + if (Flags.unfoldAnimationBackgroundProgress()) { + unfoldTransitionProgressProvider = getBgUnfoldTransitionProgressProvider(); + } else { + unfoldTransitionProgressProvider = getUnfoldTransitionProgressProvider(); + } + unfoldTransitionProgressProvider + .ifPresent( + (progressProvider) -> + getUnfoldTransitionProgressForwarder() + .ifPresent(progressProvider::addCallback)); } /** @@ -169,13 +180,14 @@ public interface SysUIComponent { ContextComponentHelper getContextComponentHelper(); /** - * Creates a UnfoldLatencyTracker. + * Creates a UnfoldTransitionProgressProvider that calculates progress in the background. */ @SysUISingleton - UnfoldLatencyTracker getUnfoldLatencyTracker(); + @UnfoldBg + Optional<UnfoldTransitionProgressProvider> getBgUnfoldTransitionProgressProvider(); /** - * Creates a UnfoldTransitionProgressProvider. + * Creates a UnfoldTransitionProgressProvider that calculates progress in the main thread. */ @SysUISingleton Optional<UnfoldTransitionProgressProvider> getUnfoldTransitionProgressProvider(); @@ -219,11 +231,6 @@ public interface SysUIComponent { */ Optional<SysUIUnfoldComponent> getSysUIUnfoldComponent(); - /** - * For devices with a hinge: the rotation animation - */ - Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider(); - /** */ MediaMuteAwaitConnectionCli getMediaMuteAwaitConnectionCli(); diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index 26c5ea6e164d..c93b8e1a48f2 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -29,7 +29,6 @@ import com.android.app.tracing.FlowTracing.traceEach import com.android.app.tracing.traceSection import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.DisplayEvent import com.android.systemui.util.Compile @@ -93,7 +92,7 @@ class DisplayRepositoryImpl constructor( private val displayManager: DisplayManager, @Background backgroundHandler: Handler, - @Application applicationScope: CoroutineScope, + @Background bgApplicationScope: CoroutineScope, @Background backgroundCoroutineDispatcher: CoroutineDispatcher ) : DisplayRepository { private val allDisplayEvents: Flow<DisplayEvent> = @@ -141,8 +140,7 @@ constructor( private val enabledDisplays = allDisplayEvents .map { getDisplays() } - .flowOn(backgroundCoroutineDispatcher) - .shareIn(applicationScope, started = SharingStarted.WhileSubscribed(), replay = 1) + .shareIn(bgApplicationScope, started = SharingStarted.WhileSubscribed(), replay = 1) override val displays: Flow<Set<Display>> = enabledDisplays @@ -203,9 +201,8 @@ constructor( } .distinctUntilChanged() .debugLog("connectedDisplayIds") - .flowOn(backgroundCoroutineDispatcher) .stateIn( - applicationScope, + bgApplicationScope, started = SharingStarted.WhileSubscribed(), // The initial value is set to empty, but connected displays are gathered as soon as // the flow starts being collected. This is to ensure the call to get displays (an diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index b357b563f791..2a0d6a8a77d9 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -44,11 +44,11 @@ object Flags { // 100 - notification // TODO(b/297792660): Tracking Bug @JvmField val UNCLEARED_TRANSIENT_HUN_FIX = - unreleasedFlag("uncleared_transient_hun_fix", teamfood = false) + unreleasedFlag("uncleared_transient_hun_fix", teamfood = true) // TODO(b/298308067): Tracking Bug @JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX = - unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = false) + unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = true) // TODO(b/254512751): Tracking Bug val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = @@ -626,7 +626,7 @@ object Flags { // TODO(b/277201412): Tracking Bug @JvmField - val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = releasedFlag("split_shade_subpixel_optimization") + val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = unreleasedFlag("split_shade_subpixel_optimization") // TODO(b/288868056): Tracking Bug @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 2b1cdc2ff026..33dd3d9bea16 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -647,7 +647,7 @@ public class KeyguardService extends Service { public void dismissKeyguardToLaunch(Intent intentToLaunch) { trace("dismissKeyguardToLaunch"); checkPermission(); - mKeyguardViewMediator.dismissKeyguardToLaunch(intentToLaunch); + Slog.d(TAG, "Ignoring dismissKeyguardToLaunch " + intentToLaunch); } @Override // Binder interface diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 53ec3dead6c5..30090874a480 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -248,7 +248,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private static final int SHOW = 1; private static final int HIDE = 2; private static final int RESET = 3; - private static final int VERIFY_UNLOCK = 4; private static final int NOTIFY_FINISHED_GOING_TO_SLEEP = 5; private static final int KEYGUARD_DONE = 7; private static final int KEYGUARD_DONE_DRAWING = 8; @@ -2316,15 +2315,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mHandler.sendMessage(msg); } - /** - * Send message to keyguard telling it to verify unlock - * @see #handleVerifyUnlock() - */ - private void verifyUnlockLocked() { - if (DEBUG) Log.d(TAG, "verifyUnlockLocked"); - mHandler.sendEmptyMessage(VERIFY_UNLOCK); - } - private void notifyStartedGoingToSleep() { if (DEBUG) Log.d(TAG, "notifyStartedGoingToSleep"); mHandler.sendEmptyMessage(NOTIFY_STARTED_GOING_TO_SLEEP); @@ -2498,12 +2488,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, message = "RESET"; handleReset(msg.arg1 != 0); break; - case VERIFY_UNLOCK: - message = "VERIFY_UNLOCK"; - Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK"); - handleVerifyUnlock(); - Trace.endSection(); - break; case NOTIFY_STARTED_GOING_TO_SLEEP: message = "NOTIFY_STARTED_GOING_TO_SLEEP"; handleNotifyStartedGoingToSleep(); @@ -3435,20 +3419,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, scheduleNonStrongBiometricIdleTimeout(); } - /** - * Handle message sent by {@link #verifyUnlock} - * @see #VERIFY_UNLOCK - */ - private void handleVerifyUnlock() { - Trace.beginSection("KeyguardViewMediator#handleVerifyUnlock"); - synchronized (KeyguardViewMediator.this) { - if (DEBUG) Log.d(TAG, "handleVerifyUnlock"); - setShowingLocked(true); - mKeyguardViewControllerLazy.get().dismissAndCollapse(); - } - Trace.endSection(); - } - private void handleNotifyStartedGoingToSleep() { synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleNotifyStartedGoingToSleep"); @@ -3606,10 +3576,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // do nothing } - public void dismissKeyguardToLaunch(Intent intentToLaunch) { - // do nothing - } - public void onSystemKeyPressed(int keycode) { // do nothing } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt index f9b89b11cd67..7354cfcb170d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener import com.android.app.tracing.traceSection +import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject import javax.inject.Singleton @@ -29,7 +30,7 @@ class LifecycleScreenStatusProvider @Inject constructor(screenLifecycle: ScreenL screenLifecycle.addObserver(this) } - private val listeners: MutableList<ScreenListener> = mutableListOf() + private val listeners: MutableList<ScreenListener> = CopyOnWriteArrayList() override fun removeCallback(listener: ScreenListener) { listeners.remove(listener) 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 8ef26621362f..36412e31e7c0 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 @@ -253,7 +253,7 @@ constructor( Pair(isAuthenticated.isFalse(), "faceNotAuthenticated"), ) .andAllFlows("canFaceAuthRun", faceAuthLog) - .flowOn(mainDispatcher) + .flowOn(backgroundDispatcher) .stateIn(applicationScope, SharingStarted.Eagerly, false) // Face detection can run only when lockscreen bypass is enabled @@ -280,7 +280,7 @@ constructor( ) ) .andAllFlows("canFaceDetectRun", faceDetectLog) - .flowOn(mainDispatcher) + .flowOn(backgroundDispatcher) .stateIn(applicationScope, SharingStarted.Eagerly, false) observeFaceAuthGatingChecks() observeFaceDetectGatingChecks() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt index 9bec30052476..96386f3dc596 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt @@ -26,6 +26,7 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus @@ -33,11 +34,13 @@ import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatu import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.stateIn /** Encapsulates state about device entry fingerprint auth mechanism. */ @@ -74,6 +77,7 @@ constructor( val authController: AuthController, val keyguardUpdateMonitor: KeyguardUpdateMonitor, @Application scope: CoroutineScope, + @Main private val mainDispatcher: CoroutineDispatcher, ) : DeviceEntryFingerprintAuthRepository { override val availableFpSensorType: Flow<BiometricType?> @@ -137,30 +141,34 @@ constructor( .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) override val isRunning: Flow<Boolean> - get() = conflatedCallbackFlow { - val callback = - object : KeyguardUpdateMonitorCallback() { - override fun onBiometricRunningStateChanged( - running: Boolean, - biometricSourceType: BiometricSourceType? - ) { - if (biometricSourceType == BiometricSourceType.FINGERPRINT) { - trySendWithFailureLogging( - running, - TAG, - "Fingerprint running state changed" - ) + get() = + conflatedCallbackFlow { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onBiometricRunningStateChanged( + running: Boolean, + biometricSourceType: BiometricSourceType? + ) { + if (biometricSourceType == BiometricSourceType.FINGERPRINT) { + trySendWithFailureLogging( + running, + TAG, + "Fingerprint running state changed" + ) + } + } } - } + keyguardUpdateMonitor.registerCallback(callback) + trySendWithFailureLogging( + keyguardUpdateMonitor.isFingerprintDetectionRunning, + TAG, + "Initial fingerprint running state" + ) + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } - keyguardUpdateMonitor.registerCallback(callback) - trySendWithFailureLogging( - keyguardUpdateMonitor.isFingerprintDetectionRunning, - TAG, - "Initial fingerprint running state" - ) - awaitClose { keyguardUpdateMonitor.removeCallback(callback) } - } + .flowOn( + mainDispatcher + ) // keyguardUpdateMonitor requires registration on main thread. override val authenticationStatus: Flow<FingerprintAuthenticationStatus> get() = conflatedCallbackFlow { @@ -171,7 +179,6 @@ constructor( biometricSourceType: BiometricSourceType, isStrongBiometric: Boolean, ) { - sendUpdateIfFingerprint( biometricSourceType, SuccessFingerprintAuthenticationStatus( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt index adb1e01d0d00..7c430920cb46 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt @@ -19,11 +19,14 @@ package com.android.systemui.keyguard.data.repository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.shared.model.DevicePosture import com.android.systemui.statusbar.policy.DevicePostureController import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn /** Provide current device posture state. */ interface DevicePostureRepository { @@ -34,23 +37,28 @@ interface DevicePostureRepository { @SysUISingleton class DevicePostureRepositoryImpl @Inject -constructor(private val postureController: DevicePostureController) : DevicePostureRepository { +constructor( + private val postureController: DevicePostureController, + @Main private val mainDispatcher: CoroutineDispatcher +) : DevicePostureRepository { override val currentDevicePosture: Flow<DevicePosture> - get() = conflatedCallbackFlow { - val sendPostureUpdate = { posture: Int -> - val currentDevicePosture = DevicePosture.toPosture(posture) - trySendWithFailureLogging( - currentDevicePosture, - TAG, - "Error sending posture update to $currentDevicePosture" - ) - } - val callback = DevicePostureController.Callback { sendPostureUpdate(it) } - postureController.addCallback(callback) - sendPostureUpdate(postureController.devicePosture) + get() = + conflatedCallbackFlow { + val sendPostureUpdate = { posture: Int -> + val currentDevicePosture = DevicePosture.toPosture(posture) + trySendWithFailureLogging( + currentDevicePosture, + TAG, + "Error sending posture update to $currentDevicePosture" + ) + } + val callback = DevicePostureController.Callback { sendPostureUpdate(it) } + postureController.addCallback(callback) + sendPostureUpdate(postureController.devicePosture) - awaitClose { postureController.removeCallback(callback) } - } + awaitClose { postureController.removeCallback(callback) } + } + .flowOn(mainDispatcher) // DevicePostureController requirement companion object { const val TAG = "PostureRepositoryImpl" 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 91b671599eb0..e58d7710877b 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 @@ -27,8 +27,8 @@ import com.android.keyguard.KeyguardClockSwitch.ClockSize import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.common.shared.model.NotificationContainerBounds 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.keyguard.data.repository.KeyguardRepository @@ -82,15 +82,14 @@ constructor( sceneInteractorProvider: Provider<SceneInteractor>, ) { // TODO(b/296118689): move to a repository - private val _sharedNotificationContainerPosition = - MutableStateFlow(SharedNotificationContainerPosition()) + private val _sharedNotificationContainerBounds = MutableStateFlow(NotificationContainerBounds()) - /** Position information for the shared notification container. */ - val sharedNotificationContainerPosition: StateFlow<SharedNotificationContainerPosition> = - _sharedNotificationContainerPosition.asStateFlow() + /** Bounds of the notification container. */ + val notificationContainerBounds: StateFlow<NotificationContainerBounds> = + _sharedNotificationContainerBounds.asStateFlow() - fun setSharedNotificationContainerPosition(position: SharedNotificationContainerPosition) { - _sharedNotificationContainerPosition.value = position + fun setNotificationContainerBounds(position: NotificationContainerBounds) { + _sharedNotificationContainerBounds.value = position } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 4da48f697b0f..706aba3c0505 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -302,4 +302,11 @@ constructor( fun isFinishedInState(state: KeyguardState): Flow<Boolean> { return finishedKeyguardState.map { it == state }.distinctUntilChanged() } + + /** + * Whether we've FINISHED a transition to a state that matches the given predicate. Consider + * using [isFinishedInStateWhere] whenever possible instead + */ + fun isFinishedInStateWhereValue(stateMatcher: (KeyguardState) -> Boolean) = + stateMatcher(finishedKeyguardState.replayCache.last()) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/SwipeUpAnywhereGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/SwipeUpAnywhereGestureHandler.kt new file mode 100644 index 000000000000..3540a0c6f016 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/SwipeUpAnywhereGestureHandler.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.keyguard.ui + +import android.content.Context +import android.view.MotionEvent +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.settings.DisplayTracker +import com.android.systemui.statusbar.gesture.SwipeUpGestureHandler +import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger +import javax.inject.Inject + +/** A class to detect when a user swipes up anywhere on the display. */ +@SysUISingleton +class SwipeUpAnywhereGestureHandler +@Inject +constructor( + context: Context, + displayTracker: DisplayTracker, + logger: SwipeUpGestureLogger, +) : + SwipeUpGestureHandler( + context, + displayTracker, + logger, + loggerTag = "SwipeUpAnywhereGestureHandler" + ) { + override fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean { + return true + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index a6383eb5f785..594865d38600 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -20,23 +20,21 @@ import android.view.View import android.view.ViewGroup import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.classifier.Classifier import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor +import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.scrim.ScrimView import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.gesture.TapGestureDetector import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch -/** - * Binds the alternate bouncer view to its view-model. - * - * To use this properly, users should maintain a one-to-one relationship between the [View] and the - * view-binding, binding each view only once. It is okay and expected for the same instance of the - * view-model to be reused for multiple view/view-binder bindings. - */ +/** Binds the alternate bouncer view to its view-model. */ @ExperimentalCoroutinesApi object AlternateBouncerViewBinder { @@ -47,6 +45,9 @@ object AlternateBouncerViewBinder { viewModel: AlternateBouncerViewModel, scope: CoroutineScope, notificationShadeWindowController: NotificationShadeWindowController, + falsingManager: FalsingManager, + swipeUpAnywhereGestureHandler: SwipeUpAnywhereGestureHandler, + tapGestureDetector: TapGestureDetector, ) { DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode() scope.launch { @@ -64,9 +65,25 @@ object AlternateBouncerViewBinder { scrim.viewAlpha = 0f launch { - viewModel.onClickListener.collect { - // TODO (b/287599719): Support swiping to dismiss altBouncer - alternateBouncerViewContainer.setOnClickListener(it) + viewModel.registerForDismissGestures.collect { registerForDismissGestures -> + if (registerForDismissGestures) { + swipeUpAnywhereGestureHandler.addOnGestureDetectedCallback(swipeTag) { _ + -> + if ( + !falsingManager.isFalseTouch(Classifier.ALTERNATE_BOUNCER_SWIPE) + ) { + viewModel.showPrimaryBouncer() + } + } + tapGestureDetector.addOnGestureDetectedCallback(tapTag) { _ -> + if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + viewModel.showPrimaryBouncer() + } + } + } else { + swipeUpAnywhereGestureHandler.removeOnGestureDetectedCallback(swipeTag) + tapGestureDetector.removeOnGestureDetectedCallback(tapTag) + } } } @@ -83,3 +100,6 @@ object AlternateBouncerViewBinder { } } } + +private const val swipeTag = "AlternateBouncer-SWIPE" +private const val tapTag = "AlternateBouncer-TAP" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 114fd94ebeb2..c0d3d336719e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -308,7 +308,7 @@ object KeyguardRootViewBinder { private class OnLayoutChange(private val viewModel: KeyguardRootViewModel) : OnLayoutChangeListener { override fun onLayoutChange( - v: View, + view: View, left: Int, top: Int, right: Int, @@ -318,18 +318,16 @@ object KeyguardRootViewBinder { oldRight: Int, oldBottom: Int ) { - val nsslPlaceholder = v.findViewById(R.id.nssl_placeholder) as View? - if (nsslPlaceholder != null) { + view.findViewById<View>(R.id.nssl_placeholder)?.let { notificationListPlaceholder -> // After layout, ensure the notifications are positioned correctly - viewModel.onSharedNotificationContainerPositionChanged( - nsslPlaceholder.top.toFloat(), - nsslPlaceholder.bottom.toFloat(), + viewModel.onNotificationContainerBoundsChanged( + notificationListPlaceholder.top.toFloat(), + notificationListPlaceholder.bottom.toFloat(), ) } - val ksv = v.findViewById(R.id.keyguard_status_view) as View? - if (ksv != null) { - viewModel.statusViewTop = ksv.top + view.findViewById<View>(R.id.keyguard_status_view)?.let { statusView -> + viewModel.statusViewTop = statusView.top } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index 27b38c71d2e7..fa27442707a3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -24,7 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.items.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection @@ -49,7 +49,7 @@ class DefaultKeyguardBlueprint @Inject constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, - defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, + defaultDeviceEntrySection: DefaultDeviceEntrySection, defaultShortcutsSection: DefaultShortcutsSection, @Named(KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) defaultAmbientIndicationAreaSection: Optional<KeyguardSection>, @@ -79,7 +79,7 @@ constructor( communalTutorialIndicatorSection, clockSection, smartspaceSection, - defaultDeviceEntryIconSection, // Add LAST: Intentionally has z-order above other views. + defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views. ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index 190ad44845d0..bf7068220576 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -23,7 +23,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection @@ -42,7 +42,7 @@ class ShortcutsBesideUdfpsKeyguardBlueprint @Inject constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, - defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, + defaultDeviceEntrySection: DefaultDeviceEntrySection, @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) defaultAmbientIndicationAreaSection: Optional<KeyguardSection>, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, @@ -68,7 +68,7 @@ constructor( splitShadeGuidelines, aodNotificationIconsSection, aodBurnInSection, - defaultDeviceEntryIconSection, // Add LAST: Intentionally has z-order above other views. + defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views. ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt index acbcf273214b..f890ae612ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt @@ -23,7 +23,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection @@ -47,7 +47,7 @@ class SplitShadeKeyguardBlueprint @Inject constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, - defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, + defaultDeviceEntrySection: DefaultDeviceEntrySection, defaultShortcutsSection: DefaultShortcutsSection, @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) defaultAmbientIndicationAreaSection: Optional<KeyguardSection>, @@ -75,7 +75,7 @@ constructor( aodNotificationIconsSection, aodBurnInSection, communalTutorialIndicatorSection, - defaultDeviceEntryIconSection, // Add LAST: Intentionally has z-order above other views. + defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views. ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index fac84981577f..77ab9f416910 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -37,6 +37,7 @@ import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder import com.android.systemui.keyguard.ui.view.DeviceEntryIconView @@ -48,6 +49,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.gesture.TapGestureDetector import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -55,7 +57,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi /** Includes both the device entry icon and the alternate bouncer scrim. */ @ExperimentalCoroutinesApi -class DefaultDeviceEntryIconSection +class DefaultDeviceEntrySection @Inject constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, @@ -72,6 +74,8 @@ constructor( private val alternateBouncerViewModel: Lazy<AlternateBouncerViewModel>, private val notificationShadeWindowController: Lazy<NotificationShadeWindowController>, @Application private val scope: CoroutineScope, + private val swipeUpAnywhereGestureHandler: Lazy<SwipeUpAnywhereGestureHandler>, + private val tapGestureDetector: Lazy<TapGestureDetector>, ) : KeyguardSection() { private val deviceEntryIconViewId = R.id.device_entry_icon_view private val alternateBouncerViewId = R.id.alternate_bouncer @@ -118,6 +122,9 @@ constructor( alternateBouncerViewModel.get(), scope, notificationShadeWindowController.get(), + falsingManager.get(), + swipeUpAnywhereGestureHandler.get(), + tapGestureDetector.get(), ) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt index 235a28d4ebc5..bb7bcd99ffb6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt @@ -18,12 +18,10 @@ package com.android.systemui.keyguard.ui.viewmodel import android.graphics.Color -import android.view.View import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TRANSITION_DURATION_MS import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow -import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.wm.shell.animation.Interpolators import javax.inject.Inject @@ -38,9 +36,8 @@ import kotlinx.coroutines.flow.merge class AlternateBouncerViewModel @Inject constructor( - statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, transitionInteractor: KeyguardTransitionInteractor, - falsingManager: FalsingManager, ) { // When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be: private val alternateBouncerScrimAlpha = .66f @@ -83,21 +80,10 @@ constructor( /** An observable for the scrim color. Change color for easier debugging. */ val scrimColor: Flow<Int> = flowOf(Color.BLACK) - private val clickListener = - View.OnClickListener { - if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true) - } - } + val registerForDismissGestures: Flow<Boolean> = + transitionToAlternateBouncerProgress.map { it == 1f }.distinctUntilChanged() - val onClickListener: Flow<View.OnClickListener?> = - transitionToAlternateBouncerProgress - .map { - if (it == 1f) { - clickListener - } else { - null - } - } - .distinctUntilChanged() + fun showPrimaryBouncer() { + statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true) + } } 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 b3c7d3790527..524fa1ede90a 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 @@ -22,7 +22,7 @@ import android.util.MathUtils import android.view.View.VISIBLE import com.android.app.animation.Interpolators import com.android.systemui.Flags.newAodTransition -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.BurnInInteractor @@ -98,9 +98,9 @@ constructor( val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition - /** the shared notification container position *on the lockscreen* */ - val notificationPositionOnLockscreen: StateFlow<SharedNotificationContainerPosition> - get() = keyguardInteractor.sharedNotificationContainerPosition + /** the shared notification container bounds *on the lockscreen* */ + val notificationBounds: StateFlow<NotificationContainerBounds> = + keyguardInteractor.notificationContainerBounds /** An observable for the alpha level for the entire keyguard root view. */ val alpha: Flow<Float> = @@ -247,14 +247,13 @@ constructor( previewMode.value = PreviewMode(true) } - fun onSharedNotificationContainerPositionChanged(top: Float, bottom: Float) { + fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) { // Notifications should not be visible in preview mode if (previewMode.value.isInPreviewMode) { return } - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top, bottom) - ) + + keyguardInteractor.setNotificationContainerBounds(NotificationContainerBounds(top, bottom)) } /** Is there an expanded pulse, are we animating in response? */ diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardMediaControllerLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardMediaControllerLog.kt new file mode 100644 index 000000000000..346f269edbb2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardMediaControllerLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger + +import javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for KeyguardMediaController. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class KeyguardMediaControllerLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 0d81940cacbd..0b3bbb5c3d08 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -156,6 +156,14 @@ public class LogModule { return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */); } + /** Provides a logging buffer for all logs related to keyguard media controller. */ + @Provides + @SysUISingleton + @KeyguardMediaControllerLog + public static LogBuffer provideKeyguardMediaControllerLogBuffer(LogBufferFactory factory) { + return factory.create("KeyguardMediaControllerLog", 50 /* maxSize */, false /* systrace */); + } + /** Provides a logging buffer for all logs related to unseen notifications. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 773c292befcf..945bf9a5c0b2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -23,7 +23,6 @@ import android.net.Uri import android.os.Handler import android.os.UserHandle import android.provider.Settings -import android.util.Log import android.view.View import android.view.ViewGroup import androidx.annotation.VisibleForTesting @@ -63,22 +62,21 @@ constructor( @Main private val handler: Handler, configurationController: ConfigurationController, private val splitShadeStateController: SplitShadeStateController, + private val logger: KeyguardMediaControllerLogger, dumpManager: DumpManager, ) : Dumpable { - /** It's added for debugging purpose to directly see last received StatusBarState. */ - private var lastReceivedStatusBarState = -1 + private var lastUsedStatusBarState = -1 init { dumpManager.registerDumpable(this) statusBarStateController.addCallback( object : StatusBarStateController.StateListener { override fun onStateChanged(newState: Int) { - lastReceivedStatusBarState = newState - refreshMediaPosition() + refreshMediaPosition(reason = "StatusBarState.onStateChanged") } override fun onDozingChanged(isDozing: Boolean) { - refreshMediaPosition() + refreshMediaPosition(reason = "StatusBarState.onDozingChanged") } } ) @@ -100,7 +98,7 @@ constructor( true, UserHandle.USER_CURRENT ) - refreshMediaPosition() + refreshMediaPosition(reason = "allowMediaPlayerOnLockScreen changed") } } } @@ -132,7 +130,7 @@ constructor( } field = value reattachHostView() - refreshMediaPosition() + refreshMediaPosition(reason = "useSplitShade changed") } /** Is the media player visible? */ @@ -147,7 +145,7 @@ constructor( var isDozeWakeUpAnimationWaiting: Boolean = false set(value) { field = value - refreshMediaPosition() + refreshMediaPosition(reason = "isDozeWakeUpAnimationWaiting changed") } /** single pane media container placed at the top of the notifications list */ @@ -181,7 +179,7 @@ constructor( /** Called whenever the media hosts visibility changes */ private fun onMediaHostVisibilityChanged(visible: Boolean) { - refreshMediaPosition() + refreshMediaPosition(reason = "onMediaHostVisibilityChanged") if (visible) { mediaHost.hostView.layoutParams.apply { height = ViewGroup.LayoutParams.WRAP_CONTENT @@ -194,7 +192,7 @@ constructor( fun attachSplitShadeContainer(container: ViewGroup) { splitShadeContainer = container reattachHostView() - refreshMediaPosition() + refreshMediaPosition(reason = "attachSplitShadeContainer") } private fun reattachHostView() { @@ -217,30 +215,41 @@ constructor( } } - fun refreshMediaPosition() { + fun refreshMediaPosition(reason: String) { val currentState = statusBarStateController.state - if (lastReceivedStatusBarState != -1 && currentState != lastReceivedStatusBarState) { - Log.wtfStack( - TAG, - "currentState[${StatusBarState.toString(currentState)}] is " + - "different from the last " + - "received one[${StatusBarState.toString(lastReceivedStatusBarState)}]." - ) - } val keyguardOrUserSwitcher = (currentState == StatusBarState.KEYGUARD) // mediaHost.visible required for proper animations handling + val isMediaHostVisible = mediaHost.visible + val isBypassNotEnabled = !bypassController.bypassEnabled + val currentAllowMediaPlayerOnLockScreen = allowMediaPlayerOnLockScreen + val useSplitShade = useSplitShade + val shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade() + visible = - mediaHost.visible && - !bypassController.bypassEnabled && + isMediaHostVisible && + isBypassNotEnabled && keyguardOrUserSwitcher && - allowMediaPlayerOnLockScreen && - shouldBeVisibleForSplitShade() + currentAllowMediaPlayerOnLockScreen && + shouldBeVisibleForSplitShade if (visible) { showMediaPlayer() } else { hideMediaPlayer() } + logger.logRefreshMediaPosition( + reason = reason, + visible = visible, + useSplitShade = useSplitShade, + currentState = currentState, + keyguardOrUserSwitcher = keyguardOrUserSwitcher, + mediaHostVisible = isMediaHostVisible, + bypassNotEnabled = isBypassNotEnabled, + currentAllowMediaPlayerOnLockScreen = currentAllowMediaPlayerOnLockScreen, + shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade + ) + + lastUsedStatusBarState = currentState } private fun shouldBeVisibleForSplitShade(): Boolean { @@ -298,10 +307,10 @@ constructor( println("isDozeWakeUpAnimationWaiting", isDozeWakeUpAnimationWaiting) println("singlePaneContainer", singlePaneContainer) println("splitShadeContainer", splitShadeContainer) - if (lastReceivedStatusBarState != -1) { + if (lastUsedStatusBarState != -1) { println( - "lastReceivedStatusBarState", - StatusBarState.toString(lastReceivedStatusBarState) + "lastUsedStatusBarState", + StatusBarState.toString(lastUsedStatusBarState) ) } println( @@ -311,8 +320,4 @@ constructor( } } } - - private companion object { - private const val TAG = "KeyguardMediaController" - } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt new file mode 100644 index 000000000000..41fef88645a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.controls.ui + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.dagger.KeyguardMediaControllerLog +import com.android.systemui.statusbar.StatusBarState +import javax.inject.Inject + +/** Logger class for [KeyguardMediaController]. */ +open class KeyguardMediaControllerLogger +@Inject +constructor(@KeyguardMediaControllerLog private val logBuffer: LogBuffer) { + + fun logRefreshMediaPosition( + reason: String, + visible: Boolean, + useSplitShade: Boolean, + currentState: Int, + keyguardOrUserSwitcher: Boolean, + mediaHostVisible: Boolean, + bypassNotEnabled: Boolean, + currentAllowMediaPlayerOnLockScreen: Boolean, + shouldBeVisibleForSplitShade: Boolean + ) = + logBuffer.log( + TAG, + DEBUG, + { + str1 = reason + bool1 = visible + bool2 = useSplitShade + int1 = currentState + bool3 = keyguardOrUserSwitcher + bool4 = mediaHostVisible + int2 = if (bypassNotEnabled) 1 else 0 + str2 = currentAllowMediaPlayerOnLockScreen.toString() + str3 = shouldBeVisibleForSplitShade.toString() + }, + { + "refreshMediaPosition(reason=$str1, " + + "currentState=${StatusBarState.toString(int1)}, " + + "visible=$bool1, useSplitShade=$bool2, " + + "keyguardOrUserSwitcher=$bool3, " + + "mediaHostVisible=$bool4, " + + "bypassNotEnabled=${int2 == 1}, " + + "currentAllowMediaPlayerOnLockScreen=$str2, " + + "shouldBeVisibleForSplitShade=$str3)" + } + ) + + private companion object { + private const val TAG = "KeyguardMediaControllerLog" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 0c5a14f5720a..48f432e6a6ea 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -58,8 +58,8 @@ import androidx.core.graphics.drawable.IconCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastSender; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import java.util.concurrent.Executor; @@ -85,6 +85,13 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements final MediaOutputController mMediaOutputController; final BroadcastSender mBroadcastSender; + /** + * Signals whether the dialog should NOT show app-related metadata. + * + * <p>A metadata-less dialog hides the title, subtitle, and app icon in the header. + */ + private final boolean mIncludePlaybackAndAppMetadata; + @VisibleForTesting View mDialogView; private TextView mHeaderTitle; @@ -210,8 +217,11 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } } - public MediaOutputBaseDialog(Context context, BroadcastSender broadcastSender, - MediaOutputController mediaOutputController) { + public MediaOutputBaseDialog( + Context context, + BroadcastSender broadcastSender, + MediaOutputController mediaOutputController, + boolean includePlaybackAndAppMetadata) { super(context, R.style.Theme_SystemUI_Dialog_Media); // Save the context that is wrapped with our theme. @@ -226,6 +236,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mListPaddingTop = mContext.getResources().getDimensionPixelSize( R.dimen.media_output_dialog_list_padding_top); mExecutor = Executors.newSingleThreadExecutor(); + mIncludePlaybackAndAppMetadata = includePlaybackAndAppMetadata; } @Override @@ -354,7 +365,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements updateDialogBackgroundColor(); mHeaderIcon.setVisibility(View.GONE); } - if (appSourceIcon != null) { + + if (!mIncludePlaybackAndAppMetadata) { + mAppResourceIcon.setVisibility(View.GONE); + } else if (appSourceIcon != null) { Icon appIcon = appSourceIcon.toIcon(mContext); mAppResourceIcon.setColorFilter(mMediaOutputController.getColorItemContent()); mAppResourceIcon.setImageIcon(appIcon); @@ -373,17 +387,24 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size)); } mAppButton.setText(mMediaOutputController.getAppSourceName()); - // Update title and subtitle - mHeaderTitle.setText(getHeaderText()); - final CharSequence subTitle = getHeaderSubtitle(); - if (TextUtils.isEmpty(subTitle)) { + + if (!mIncludePlaybackAndAppMetadata) { + mHeaderTitle.setVisibility(View.GONE); mHeaderSubtitle.setVisibility(View.GONE); - mHeaderTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); } else { - mHeaderSubtitle.setVisibility(View.VISIBLE); - mHeaderSubtitle.setText(subTitle); - mHeaderTitle.setGravity(Gravity.NO_GRAVITY); + // Update title and subtitle + mHeaderTitle.setText(getHeaderText()); + final CharSequence subTitle = getHeaderSubtitle(); + if (TextUtils.isEmpty(subTitle)) { + mHeaderSubtitle.setVisibility(View.GONE); + mHeaderTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); + } else { + mHeaderSubtitle.setVisibility(View.VISIBLE); + mHeaderSubtitle.setText(subTitle); + mHeaderTitle.setGravity(Gravity.NO_GRAVITY); + } } + // Show when remote media session is available or // when the device supports BT LE audio + media is playing mStopButton.setVisibility(getStopButtonVisibility()); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index ac64300a6570..8e0191ec330f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -42,12 +42,10 @@ import androidx.annotation.NonNull; import androidx.core.graphics.drawable.IconCompat; import com.android.internal.annotations.VisibleForTesting; -import com.android.settingslib.media.BluetoothMediaDevice; -import com.android.settingslib.media.MediaDevice; import com.android.settingslib.qrcode.QrCodeGenerator; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.google.zxing.WriterException; @@ -237,7 +235,11 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, MediaOutputController mediaOutputController) { - super(context, broadcastSender, mediaOutputController); + super( + context, + broadcastSender, + mediaOutputController, /* includePlaybackAndAppMetadata */ + true); mAdapter = new MediaOutputAdapter(mMediaOutputController); // TODO(b/226710953): Move the part to MediaOutputBaseDialog for every class // that extends MediaOutputBaseDialog diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 426a497fa329..375a0ce55ac0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -78,7 +78,6 @@ import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.utils.ThreadUtils; -import com.android.systemui.res.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; @@ -86,6 +85,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.monet.ColorScheme; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; @@ -358,7 +358,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } Drawable getAppSourceIconFromPackage() { - if (mPackageName.isEmpty()) { + if (TextUtils.isEmpty(mPackageName)) { return null; } try { @@ -372,7 +372,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } String getAppSourceName() { - if (mPackageName.isEmpty()) { + if (TextUtils.isEmpty(mPackageName)) { return null; } final PackageManager packageManager = mContext.getPackageManager(); @@ -391,7 +391,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } Intent getAppLaunchIntent() { - if (mPackageName.isEmpty()) { + if (TextUtils.isEmpty(mPackageName)) { return null; } return mContext.getPackageManager().getLaunchIntentForPackage(mPackageName); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index 4640a5d4d801..d40699ca088c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -27,10 +27,10 @@ import androidx.core.graphics.drawable.IconCompat; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; -import com.android.systemui.res.R; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.res.R; /** * Dialog for media output transferring. @@ -40,10 +40,15 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { private final DialogLaunchAnimator mDialogLaunchAnimator; private final UiEventLogger mUiEventLogger; - MediaOutputDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, - MediaOutputController mediaOutputController, DialogLaunchAnimator dialogLaunchAnimator, - UiEventLogger uiEventLogger) { - super(context, broadcastSender, mediaOutputController); + MediaOutputDialog( + Context context, + boolean aboveStatusbar, + BroadcastSender broadcastSender, + MediaOutputController mediaOutputController, + DialogLaunchAnimator dialogLaunchAnimator, + UiEventLogger uiEventLogger, + boolean includePlaybackAndAppMetadata) { + super(context, broadcastSender, mediaOutputController, includePlaybackAndAppMetadata); mDialogLaunchAnimator = dialogLaunchAnimator; mUiEventLogger = uiEventLogger; mAdapter = new MediaOutputAdapter(mMediaOutputController); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt index 2b38edb3c47e..b04a7a4fd155 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt @@ -61,6 +61,19 @@ open class MediaOutputDialogFactory @Inject constructor( /** Creates a [MediaOutputDialog] for the given package. */ open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) { + create(packageName, aboveStatusBar, view, includePlaybackAndAppMetadata = true) + } + + open fun createDialogForSystemRouting() { + create(packageName = null, aboveStatusBar = false, includePlaybackAndAppMetadata = false) + } + + private fun create( + packageName: String?, + aboveStatusBar: Boolean, + view: View? = null, + includePlaybackAndAppMetadata: Boolean = true + ) { // Dismiss the previous dialog, if any. mediaOutputDialog?.dismiss() @@ -71,7 +84,7 @@ open class MediaOutputDialogFactory @Inject constructor( powerExemptionManager, keyGuardManager, featureFlags, userTracker) val dialog = MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, - dialogLaunchAnimator, uiEventLogger) + dialogLaunchAnimator, uiEventLogger, includePlaybackAndAppMetadata) mediaOutputDialog = dialog // Show the dialog. diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt index 132bf99c3f62..1002cc3bd3bb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt @@ -19,7 +19,6 @@ package com.android.systemui.media.dialog import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.text.TextUtils import android.util.Log import com.android.settingslib.media.MediaOutputConstants import javax.inject.Inject @@ -35,16 +34,16 @@ class MediaOutputDialogReceiver @Inject constructor( private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory ) : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - when { - TextUtils.equals( - MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, intent.action) -> { + when (intent.action) { + MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG -> { val packageName: String? = intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME) launchMediaOutputDialogIfPossible(packageName) } - TextUtils.equals( - MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG, - intent.action) -> { + MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG -> { + mediaOutputDialogFactory.createDialogForSystemRouting() + } + MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG -> { val packageName: String? = intent.getStringExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME) launchMediaOutputBroadcastDialogIfPossible(packageName) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt index 654fffe89471..1983a670b5a8 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt @@ -24,6 +24,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewStub import android.view.WindowManager +import android.view.accessibility.AccessibilityNodeInfo import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.ImageView @@ -106,6 +107,19 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>( screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_spinner) screenShareModeSpinner.adapter = adapter screenShareModeSpinner.onItemSelectedListener = this + + // disable redundant Touch & Hold accessibility action for Switch Access + screenShareModeSpinner.accessibilityDelegate = + object : View.AccessibilityDelegate() { + override fun onInitializeAccessibilityNodeInfo( + host: View, + info: AccessibilityNodeInfo + ) { + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK) + super.onInitializeAccessibilityNodeInfo(host, info) + } + } + screenShareModeSpinner.isLongClickable = false } override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 5e3a166f5f35..e2e94dae900a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -72,6 +72,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.Trace; import android.provider.DeviceConfig; import android.telecom.TelecomManager; import android.text.TextUtils; @@ -736,17 +737,27 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } public void destroyView() { - setAutoHideController(/* autoHideController */ null); - mCommandQueue.removeCallback(this); - mWindowManager.removeViewImmediate(mView.getRootView()); - mNavigationModeController.removeListener(mModeChangedListener); - mEdgeBackGestureHandler.setStateChangeCallback(null); + Trace.beginSection("NavigationBar#destroyView"); + try { + setAutoHideController(/* autoHideController */ null); + mCommandQueue.removeCallback(this); + Trace.beginSection("NavigationBar#removeViewImmediate"); + try { + mWindowManager.removeViewImmediate(mView.getRootView()); + } finally { + Trace.endSection(); + } + mNavigationModeController.removeListener(mModeChangedListener); + mEdgeBackGestureHandler.setStateChangeCallback(null); - mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); - mNotificationShadeDepthController.removeListener(mDepthListener); + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + mNotificationShadeDepthController.removeListener(mDepthListener); - mDeviceConfigProxy.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); - mTaskStackChangeListeners.unregisterTaskStackListener(mTaskStackListener); + mDeviceConfigProxy.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); + mTaskStackChangeListeners.unregisterTaskStackListener(mTaskStackListener); + } finally { + Trace.endSection(); + } } @Override @@ -940,50 +951,47 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private void orientSecondaryHomeHandle() { if (!canShowSecondaryHandle()) { + if (mStartingQuickSwitchRotation == -1) { + resetSecondaryHandle(); + } return; } - if (mStartingQuickSwitchRotation == -1) { - resetSecondaryHandle(); - } else { - int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation); - if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) { - // Curious if starting quickswitch can change between the if check and our delta - Log.d(TAG, "secondary nav delta rotation: " + deltaRotation - + " current: " + mCurrentRotation - + " starting: " + mStartingQuickSwitchRotation); - } - int height = 0; - int width = 0; - Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds(); - mOrientationHandle.setDeltaRotation(deltaRotation); - switch (deltaRotation) { - case Surface.ROTATION_90: - case Surface.ROTATION_270: - height = dispSize.height(); - width = mView.getHeight(); - break; - case Surface.ROTATION_180: - case Surface.ROTATION_0: - // TODO(b/152683657): Need to determine best UX for this - if (!mShowOrientedHandleForImmersiveMode) { - resetSecondaryHandle(); - return; - } - width = dispSize.width(); - height = mView.getHeight(); - break; - } - - mOrientationParams.gravity = - deltaRotation == Surface.ROTATION_0 ? Gravity.BOTTOM : - (deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT); - mOrientationParams.height = height; - mOrientationParams.width = width; - mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams); - mView.setVisibility(View.GONE); - mOrientationHandle.setVisibility(View.VISIBLE); + int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation); + if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) { + // Curious if starting quickswitch can change between the if check and our delta + Log.d(TAG, "secondary nav delta rotation: " + deltaRotation + + " current: " + mCurrentRotation + + " starting: " + mStartingQuickSwitchRotation); + } + int height = 0; + int width = 0; + Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds(); + mOrientationHandle.setDeltaRotation(deltaRotation); + switch (deltaRotation) { + case Surface.ROTATION_90, Surface.ROTATION_270: + height = dispSize.height(); + width = mView.getHeight(); + break; + case Surface.ROTATION_180, Surface.ROTATION_0: + // TODO(b/152683657): Need to determine best UX for this + if (!mShowOrientedHandleForImmersiveMode) { + resetSecondaryHandle(); + return; + } + width = dispSize.width(); + height = mView.getHeight(); + break; } + + mOrientationParams.gravity = + deltaRotation == Surface.ROTATION_0 ? Gravity.BOTTOM : + (deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT); + mOrientationParams.height = height; + mOrientationParams.width = width; + mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams); + mView.setVisibility(View.GONE); + mOrientationHandle.setVisibility(View.VISIBLE); } private void resetSecondaryHandle() { @@ -1786,7 +1794,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } private boolean canShowSecondaryHandle() { - return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null; + return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null + && mStartingQuickSwitchRotation != -1; } private final UserTracker.Callback mUserChangedCallback = diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 3b32313e76a0..8d1ff98a5bba 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -46,6 +46,7 @@ import android.hardware.display.DisplayManager; import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.RemoteException; +import android.os.Trace; import android.util.Log; import android.view.Display; import android.view.View; @@ -229,28 +230,34 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } public void init(int displayId) { - if (mInitialized) { - return; + Trace.beginSection("TaskbarDelegate#init"); + try { + if (mInitialized) { + return; + } + mDisplayId = displayId; + parseCurrentSysuiState(); + mCommandQueue.addCallback(this); + mOverviewProxyService.addCallback(this); + onNavigationModeChanged(mNavigationModeController.addListener(this)); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + // Initialize component callback + Display display = mDisplayManager.getDisplay(displayId); + mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null); + mScreenPinningNotify = new ScreenPinningNotify(mWindowContext); + // Set initial state for any listeners + updateSysuiFlags(); + mAutoHideController.setNavigationBar(mAutoHideUiElement); + mLightBarController.setNavigationBar(mLightBarTransitionsController); + mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener); + mEdgeBackGestureHandler.setBackAnimation(mBackAnimation); + mEdgeBackGestureHandler.onConfigurationChanged( + mContext.getResources().getConfiguration()); + mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener); + mInitialized = true; + } finally { + Trace.endSection(); } - mDisplayId = displayId; - parseCurrentSysuiState(); - mCommandQueue.addCallback(this); - mOverviewProxyService.addCallback(this); - onNavigationModeChanged(mNavigationModeController.addListener(this)); - mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); - // Initialize component callback - Display display = mDisplayManager.getDisplay(displayId); - mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null); - mScreenPinningNotify = new ScreenPinningNotify(mWindowContext); - // Set initial state for any listeners - updateSysuiFlags(); - mAutoHideController.setNavigationBar(mAutoHideUiElement); - mLightBarController.setNavigationBar(mLightBarTransitionsController); - mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener); - mEdgeBackGestureHandler.setBackAnimation(mBackAnimation); - mEdgeBackGestureHandler.onConfigurationChanged(mContext.getResources().getConfiguration()); - mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener); - mInitialized = true; } public void destroy() { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 3dfd2925a1b2..9846f4b6c980 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -578,10 +578,15 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged */ public void onNavigationModeChanged(int mode) { - mUsingThreeButtonNav = QuickStepContract.isLegacyMode(mode); - mInGestureNavMode = QuickStepContract.isGesturalMode(mode); - updateIsEnabled(); - updateCurrentUserResources(); + Trace.beginSection("EdgeBackGestureHandler#onNavigationModeChanged"); + try { + mUsingThreeButtonNav = QuickStepContract.isLegacyMode(mode); + mInGestureNavMode = QuickStepContract.isGesturalMode(mode); + updateIsEnabled(); + updateCurrentUserResources(); + } finally { + Trace.endSection(); + } } public void onNavBarTransientStateChanged(boolean isTransient) { diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index d9a8080a1e83..270bfbe4274d 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -37,9 +37,10 @@ import android.os.UserManager import android.provider.Settings import android.widget.Toast import androidx.annotation.VisibleForTesting -import com.android.systemui.res.R +import com.android.app.tracing.TraceUtils.Companion.launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE @@ -47,6 +48,7 @@ import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.shared.system.ActivityManagerKt.isInForeground import com.android.systemui.util.settings.SecureSettings @@ -54,8 +56,8 @@ import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch /** * Entry point for creating and managing note. @@ -81,7 +83,8 @@ constructor( private val devicePolicyManager: DevicePolicyManager, private val userTracker: UserTracker, private val secureSettings: SecureSettings, - @Application private val applicationScope: CoroutineScope + @Application private val applicationScope: CoroutineScope, + @Background private val bgCoroutineContext: CoroutineContext ) { @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>() @@ -172,7 +175,9 @@ constructor( ) { if (!isEnabled) return - applicationScope.launch { awaitShowNoteTaskAsUser(entryPoint, user) } + applicationScope.launch("$TAG#showNoteTaskAsUser") { + awaitShowNoteTaskAsUser(entryPoint, user) + } } private suspend fun awaitShowNoteTaskAsUser( @@ -337,7 +342,7 @@ constructor( @InternalNoteTaskApi fun launchUpdateNoteTaskAsUser(user: UserHandle) { - applicationScope.launch { + applicationScope.launch("$TAG#launchUpdateNoteTaskAsUser", bgCoroutineContext) { if (!userManager.isUserUnlocked(user)) { debugLog { "updateNoteTaskAsUserInternal call but user locked: user=$user" } return@launch diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index 338d3ed42f95..9698548dd30d 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -27,6 +27,7 @@ import android.view.ViewConfiguration import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON import com.android.systemui.settings.UserTracker @@ -52,6 +53,8 @@ constructor( /** Initializes note task related features and glue it with other parts of the SystemUI. */ fun initialize() { + debugLog { "initialize: isEnabled=$isEnabled, hasBubbles=${optionalBubbles.isEmpty}" } + // Guard against feature not being enabled or mandatory dependencies aren't available. if (!isEnabled || optionalBubbles.isEmpty) return @@ -134,12 +137,15 @@ constructor( * Tracks a [KeyEvent], and determines if it should trigger an action to show the note task. * Returns a [NoteTaskEntryPoint] if an action should be taken, and null otherwise. */ - private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? = - when { + private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? { + val entryPoint = when { keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT else -> null } + debugLog { "toNoteTaskEntryPointOrNull: entryPoint=$entryPoint" } + return entryPoint + } private var lastStylusButtonTailUpEventTime: Long = -MULTI_PRESS_TIMEOUT @@ -155,8 +161,10 @@ constructor( val isMultiPress = (downTime - lastStylusButtonTailUpEventTime) < MULTI_PRESS_TIMEOUT val isLongPress = (eventTime - downTime) >= LONG_PRESS_TIMEOUT lastStylusButtonTailUpEventTime = eventTime + // For now, trigger action immediately on UP of a single press, without waiting for // the multi-press timeout to expire. + debugLog { "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress" } return !isMultiPress && !isLongPress } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt index a321eef75a14..6f5dea32bd83 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt @@ -18,17 +18,19 @@ package com.android.systemui.qs.external import android.content.ComponentName import android.content.Context +import android.content.SharedPreferences import android.service.quicksettings.Tile import android.util.Log import com.android.internal.annotations.VisibleForTesting +import javax.inject.Inject import org.json.JSONException import org.json.JSONObject -import javax.inject.Inject data class TileServiceKey(val componentName: ComponentName, val user: Int) { private val string = "${componentName.flattenToString()}:$user" override fun toString() = string } + private const val STATE = "state" private const val LABEL = "label" private const val SUBTITLE = "subtitle" @@ -44,12 +46,7 @@ private const val STATE_DESCRIPTION = "state_description" * It persists the state from a [Tile] necessary to present the view in the same state when * retrieved, with the exception of the icon. */ -class CustomTileStatePersister @Inject constructor(context: Context) { - companion object { - private const val FILE_NAME = "custom_tiles_state" - } - - private val sharedPreferences = context.getSharedPreferences(FILE_NAME, 0) +interface CustomTileStatePersister { /** * Read the state from [SharedPreferences]. @@ -58,7 +55,31 @@ class CustomTileStatePersister @Inject constructor(context: Context) { * * Any fields that have not been saved will be set to `null` */ - fun readState(key: TileServiceKey): Tile? { + fun readState(key: TileServiceKey): Tile? + /** + * Persists the state into [SharedPreferences]. + * + * The implementation does not store fields that are `null` or icons. + */ + fun persistState(key: TileServiceKey, tile: Tile) + /** + * Removes the state for a given tile, user pair. + * + * Used when the tile is removed by the user. + */ + fun removeState(key: TileServiceKey) +} + +// TODO(b/299909989) Merge this class into into CustomTileRepository +class CustomTileStatePersisterImpl @Inject constructor(context: Context) : + CustomTileStatePersister { + companion object { + private const val FILE_NAME = "custom_tiles_state" + } + + private val sharedPreferences: SharedPreferences = context.getSharedPreferences(FILE_NAME, 0) + + override fun readState(key: TileServiceKey): Tile? { val state = sharedPreferences.getString(key.toString(), null) ?: return null return try { readTileFromString(state) @@ -68,23 +89,13 @@ class CustomTileStatePersister @Inject constructor(context: Context) { } } - /** - * Persists the state into [SharedPreferences]. - * - * The implementation does not store fields that are `null` or icons. - */ - fun persistState(key: TileServiceKey, tile: Tile) { + override fun persistState(key: TileServiceKey, tile: Tile) { val state = writeToString(tile) sharedPreferences.edit().putString(key.toString(), state).apply() } - /** - * Removes the state for a given tile, user pair. - * - * Used when the tile is removed by the user. - */ - fun removeState(key: TileServiceKey) { + override fun removeState(key: TileServiceKey) { sharedPreferences.edit().remove(key.toString()).apply() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index f8e01590d368..456520051f58 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -45,6 +45,7 @@ import android.widget.LinearLayout import android.widget.Switch import android.widget.TextView import androidx.annotation.VisibleForTesting +import com.android.app.tracing.traceSection import com.android.settingslib.Utils import com.android.systemui.FontSizeUtils import com.android.systemui.animation.LaunchableView @@ -707,7 +708,7 @@ open class QSTileViewImpl @JvmOverloads constructor( inner class StateChangeRunnable(private val state: QSTile.State) : Runnable { override fun run() { - handleStateChanged(state) + traceSection("QSTileViewImpl#handleStateChanged") { handleStateChanged(state) } } // We want all instances of this runnable to be equal to each other, so they can be used to diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt index 94137c88098e..4a34276671c1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles.di +import com.android.systemui.qs.external.CustomTileStatePersister +import com.android.systemui.qs.external.CustomTileStatePersisterImpl import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerImpl import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent @@ -52,4 +54,7 @@ interface QSTilesModule { fun bindQSTileIntentUserInputHandler( impl: QSTileIntentUserInputHandlerImpl ): QSTileIntentUserInputHandler + + @Binds + fun bindCustomTileStatePersister(impl: CustomTileStatePersisterImpl): CustomTileStatePersister } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt new file mode 100644 index 000000000000..c390695b1911 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.airplane.domain + +import android.content.Context +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [AirplaneModeTileModel] to [QSTileState]. */ +class AirplaneModeMapper @Inject constructor(private val context: Context) : + QSTileDataToStateMapper<AirplaneModeTileModel> { + + override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState = + QSTileState.build(context, config.uiConfig) { + val icon = + Icon.Loaded( + context.getDrawable( + if (data.isEnabled) { + R.drawable.qs_airplane_icon_on + } else { + R.drawable.qs_airplane_icon_off + } + )!!, + contentDescription = null + ) + this.icon = { icon } + if (data.isEnabled) { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = context.resources.getStringArray(R.array.tile_states_airplane)[2] + } else { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = context.resources.getStringArray(R.array.tile_states_airplane)[1] + } + contentDescription = label + supportedActions = + setOf( + QSTileState.UserAction.CLICK, + QSTileState.UserAction.LONG_CLICK, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractor.kt new file mode 100644 index 000000000000..4f01a04fb742 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileDataInteractor.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.airplane.domain.interactor + +import android.os.UserHandle +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel +import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Observes airplane mode state changes providing the [AirplaneModeTileModel]. */ +class AirplaneModeTileDataInteractor +@Inject +constructor( + private val airplaneModeRepository: AirplaneModeRepository, +) : QSTileDataInteractor<AirplaneModeTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<AirplaneModeTileModel> = + airplaneModeRepository.isAirplaneMode.map { AirplaneModeTileModel(it) } + + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt new file mode 100644 index 000000000000..9e13a56c49a1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.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.qs.tiles.impl.airplane.domain.interactor + +import android.content.Intent +import android.provider.Settings +import android.telephony.TelephonyManager +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor +import javax.inject.Inject + +/** Handles airplane mode tile clicks and long clicks. */ +class AirplaneModeTileUserActionInteractor +@Inject +constructor( + private val airplaneModeInteractor: AirplaneModeInteractor, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, +) : QSTileUserActionInteractor<AirplaneModeTileModel> { + + override suspend fun handleInput(input: QSTileInput<AirplaneModeTileModel>) = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + when (airplaneModeInteractor.setIsAirplaneMode(!data.isEnabled)) { + AirplaneModeInteractor.SetResult.SUCCESS -> { + // do nothing + } + AirplaneModeInteractor.SetResult.BLOCKED_BY_ECM -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), + ) + } + } + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(Settings.ACTION_AIRPLANE_MODE_SETTINGS) + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/model/AirplaneModeTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/model/AirplaneModeTileModel.kt new file mode 100644 index 000000000000..7bf3b7d14d34 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/model/AirplaneModeTileModel.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.qs.tiles.impl.airplane.domain.model + +/** + * Airplane mode tile model. + * + * @param isEnabled is true when the airplane mode is enabled; + */ +@JvmInline value class AirplaneModeTileModel(val isEnabled: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt new file mode 100644 index 000000000000..869f6f321d21 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.custom.commons + +import android.service.quicksettings.Tile + +fun Tile.copy(): Tile = + Tile().also { + it.icon = icon + it.label = label + it.subtitle = subtitle + it.contentDescription = contentDescription + it.stateDescription = stateDescription + it.activityLaunchForClick = activityLaunchForClick + it.state = state + } + +fun Tile.setFrom(otherTile: Tile) { + if (otherTile.icon != null) { + icon = otherTile.icon + } + if (otherTile.customLabel != null) { + label = otherTile.customLabel + } + if (otherTile.subtitle != null) { + subtitle = otherTile.subtitle + } + if (otherTile.contentDescription != null) { + contentDescription = otherTile.contentDescription + } + if (otherTile.stateDescription != null) { + stateDescription = otherTile.stateDescription + } + activityLaunchForClick = otherTile.activityLaunchForClick + state = otherTile.state +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt new file mode 100644 index 000000000000..ca5302e13545 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.custom.data.repository + +import android.graphics.drawable.Icon +import android.os.UserHandle +import android.service.quicksettings.Tile +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.external.CustomTileStatePersister +import com.android.systemui.qs.external.TileServiceKey +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.custom.commons.copy +import com.android.systemui.qs.tiles.impl.custom.commons.setFrom +import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults +import com.android.systemui.qs.tiles.impl.di.QSTileScope +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext + +/** + * Repository store the [Tile] associated with the custom tile. It lives on [QSTileScope] which + * allows it to survive service rebinding. Given that, it provides the last received state when + * connected again. + */ +interface CustomTileRepository { + + /** + * Restores the [Tile] if it's [isPersistable]. Restored [Tile] will be available via [getTile] + * (but there is no guarantee that restoration is synchronous) and emitted in [getTiles] for a + * corresponding [user]. + */ + suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) + + /** Returns [Tile] updates for a [user]. */ + fun getTiles(user: UserHandle): Flow<Tile> + + /** + * Return current [Tile] for a [user] or null if the [user] doesn't match currently cached one. + * Suspending until [getTiles] returns something is a way to wait for this to become available. + * + * @throws IllegalStateException when there is no current tile. + */ + fun getTile(user: UserHandle): Tile? + + /** + * Updates tile with the non-null values from [newTile]. Overwrites the current cache when + * [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly + * loaded when the [restoreForTheUserIfNeeded]. + */ + suspend fun updateWithTile( + user: UserHandle, + newTile: Tile, + isPersistable: Boolean, + ) + + /** + * Updates tile with the values from [defaults]. Overwrites the current cache when [user] + * differs from the cached one. [isPersistable] tile will be persisted to be possibly loaded + * when the [restoreForTheUserIfNeeded]. + */ + suspend fun updateWithDefaults( + user: UserHandle, + defaults: CustomTileDefaults, + isPersistable: Boolean, + ) +} + +@QSTileScope +class CustomTileRepositoryImpl +@Inject +constructor( + private val tileSpec: TileSpec.CustomTileSpec, + private val customTileStatePersister: CustomTileStatePersister, + @Background private val backgroundContext: CoroutineContext, +) : CustomTileRepository { + + private val tileUpdateMutex = Mutex() + private val tileWithUserState = + MutableSharedFlow<TileWithUser>(onBufferOverflow = BufferOverflow.DROP_OLDEST, replay = 1) + + override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) { + if (isPersistable && getCurrentTileWithUser()?.user != user) { + withContext(backgroundContext) { + customTileStatePersister.readState(user.getKey())?.let { + updateWithTile( + user, + it, + true, + ) + } + } + } + } + + override fun getTiles(user: UserHandle): Flow<Tile> = + tileWithUserState.filter { it.user == user }.map { it.tile } + + override fun getTile(user: UserHandle): Tile? { + val tileWithUser = + getCurrentTileWithUser() ?: throw IllegalStateException("Tile is not set") + return if (tileWithUser.user == user) { + tileWithUser.tile + } else { + null + } + } + + override suspend fun updateWithTile( + user: UserHandle, + newTile: Tile, + isPersistable: Boolean, + ) = updateTile(user, isPersistable) { setFrom(newTile) } + + override suspend fun updateWithDefaults( + user: UserHandle, + defaults: CustomTileDefaults, + isPersistable: Boolean, + ) { + if (defaults is CustomTileDefaults.Result) { + updateTile(user, isPersistable) { + // Update the icon if it's not set or is the default icon. + val updateIcon = (icon == null || icon.isResourceEqual(defaults.icon)) + if (updateIcon) { + icon = defaults.icon + } + setDefaultLabel(defaults.label) + } + } + } + + private suspend fun updateTile( + user: UserHandle, + isPersistable: Boolean, + update: Tile.() -> Unit + ): Unit = + tileUpdateMutex.withLock { + val currentTileWithUser = getCurrentTileWithUser() + val tileToUpdate = + if (currentTileWithUser?.user == user) { + currentTileWithUser.tile.copy() + } else { + Tile() + } + tileToUpdate.update() + if (isPersistable) { + withContext(backgroundContext) { + customTileStatePersister.persistState(user.getKey(), tileToUpdate) + } + } + tileWithUserState.tryEmit(TileWithUser(user, tileToUpdate)) + } + + private fun getCurrentTileWithUser(): TileWithUser? = tileWithUserState.replayCache.lastOrNull() + + /** Compare two icons, only works for resources. */ + private fun Icon.isResourceEqual(icon2: Icon?): Boolean { + if (icon2 == null) { + return false + } + if (this === icon2) { + return true + } + if (type != Icon.TYPE_RESOURCE || icon2.type != Icon.TYPE_RESOURCE) { + return false + } + if (resId != icon2.resId) { + return false + } + return resPackage == icon2.resPackage + } + + private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier) + + private data class TileWithUser(val user: UserHandle, val tile: Tile) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt index 83767aa9d444..d956fdebcd32 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt @@ -24,6 +24,8 @@ import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl +import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository +import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepositoryImpl import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import dagger.Binds @@ -50,4 +52,6 @@ interface CustomTileModule { fun bindCustomTileDefaultsRepository( impl: CustomTileDefaultsRepositoryImpl ): CustomTileDefaultsRepository + + @Binds fun bindCustomTileRepository(impl: CustomTileRepositoryImpl): CustomTileRepository } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt new file mode 100644 index 000000000000..351bba538463 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.custom.domain.interactor + +import android.os.UserHandle +import android.service.quicksettings.Tile +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.external.TileServiceManager +import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository +import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository +import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundScope +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +/** Manages updates of the [Tile] assigned for the current custom tile. */ +@CustomTileBoundScope +class CustomTileInteractor +@Inject +constructor( + private val user: UserHandle, + private val defaultsRepository: CustomTileDefaultsRepository, + private val customTileRepository: CustomTileRepository, + private val tileServiceManager: TileServiceManager, + @CustomTileBoundScope private val boundScope: CoroutineScope, + @Background private val backgroundContext: CoroutineContext, +) { + + private val tileUpdates = + MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + + /** [Tile] updates. [updateTile] to emit a new one. */ + val tiles: Flow<Tile> + get() = customTileRepository.getTiles(user) + + /** + * Current [Tile] + * + * @throws IllegalStateException when the repository stores a tile for another user. This means + * the tile hasn't been updated for the current user. Can happen when this is accessed before + * [init] returns. + */ + val tile: Tile + get() = + customTileRepository.getTile(user) + ?: throw IllegalStateException("Attempt to get a tile for a wrong user") + + /** + * Initializes the repository for the current user. Suspends until it's safe to call [tile] + * which needs at least one of the following: + * - defaults are loaded; + * - receive tile update in [updateTile]; + * - restoration happened for a persisted tile. + */ + suspend fun init() { + launchUpdates() + customTileRepository.restoreForTheUserIfNeeded(user, tileServiceManager.isActiveTile) + // Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or + // tile update. + customTileRepository.getTiles(user).firstOrNull() + } + + private fun launchUpdates() { + tileUpdates + .onEach { + customTileRepository.updateWithTile( + user, + it, + tileServiceManager.isActiveTile, + ) + } + .flowOn(backgroundContext) + .launchIn(boundScope) + defaultsRepository + .defaults(user) + .onEach { + customTileRepository.updateWithDefaults( + user, + it, + tileServiceManager.isActiveTile, + ) + } + .flowOn(backgroundContext) + .launchIn(boundScope) + } + + /** Updates current [Tile]. Emits a new event in [tiles]. */ + fun updateTile(newTile: Tile) { + tileUpdates.tryEmit(newTile) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt new file mode 100644 index 000000000000..8e53723a5a6b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.domain + +import android.content.Context +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [LocationTileModel] to [QSTileState]. */ +class LocationTileMapper @Inject constructor(private val context: Context) : + QSTileDataToStateMapper<LocationTileModel> { + + override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState = + QSTileState.build(context, config.uiConfig) { + val icon = + Icon.Loaded( + context.resources.getDrawable( + if (data.isEnabled) { + R.drawable.qs_location_icon_on + } else { + R.drawable.qs_location_icon_off + } + ), + contentDescription = null + ) + this.icon = { icon } + + this.label = context.resources.getString(R.string.quick_settings_location_label) + + if (data.isEnabled) { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = context.resources.getStringArray(R.array.tile_states_location)[2] + } else { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = context.resources.getStringArray(R.array.tile_states_location)[1] + } + contentDescription = label + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt new file mode 100644 index 000000000000..d1c80309a1cc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.domain.interactor + +import android.os.UserHandle +import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.statusbar.policy.LocationController +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** Observes location state changes providing the [LocationTileModel]. */ +class LocationTileDataInteractor +@Inject +constructor( + private val locationController: LocationController, +) : QSTileDataInteractor<LocationTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<LocationTileModel> = + ConflatedCallbackFlow.conflatedCallbackFlow { + val initialValue = locationController.isLocationEnabled + trySend(LocationTileModel(initialValue)) + + val callback = + object : LocationController.LocationChangeCallback { + override fun onLocationSettingsChanged(locationEnabled: Boolean) { + trySend(LocationTileModel(locationEnabled)) + } + } + locationController.addCallback(callback) + awaitClose { locationController.removeCallback(callback) } + } + + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt new file mode 100644 index 000000000000..66705ead3cf0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.location.domain.interactor + +import android.content.Intent +import android.provider.Settings +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.LocationController +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** Handles location tile clicks. */ +class LocationTileUserActionInteractor +@Inject +constructor( + @Background private val coroutineContext: CoroutineContext, + @Application private val applicationScope: CoroutineScope, + private val locationController: LocationController, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, + private val activityStarter: ActivityStarter, + private val keyguardController: KeyguardStateController, +) : QSTileUserActionInteractor<LocationTileModel> { + override suspend fun handleInput(input: QSTileInput<LocationTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + val wasEnabled: Boolean = input.data.isEnabled + if (keyguardController.isMethodSecure() && keyguardController.isShowing()) { + activityStarter.postQSRunnableDismissingKeyguard { + CoroutineScope(applicationScope.coroutineContext).launch { + locationController.setLocationEnabled(!wasEnabled) + } + } + } else { + withContext(coroutineContext) { + locationController.setLocationEnabled(!wasEnabled) + } + } + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + ) + } + } + } +} diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/model/LocationTileModel.kt index afe3f07a492e..3095d7e77586 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/model/LocationTileModel.kt @@ -14,7 +14,11 @@ * limitations under the License. */ -@GraphicsMode(GraphicsMode.Mode.NATIVE) -package com.android.settingslib.spa.screenshot.widget.chart; +package com.android.systemui.qs.tiles.impl.location.domain.model -import org.robolectric.annotation.GraphicsMode; +/** + * Location tile model. + * + * @param isEnabled is true when the location is enabled; + */ +@JvmInline value class LocationTileModel(val isEnabled: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index 3afbd7c0d4ec..e8623f9f7664 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -101,7 +101,10 @@ constructor( override fun addCallback(callback: QSTile.Callback?) { callback ?: return - synchronized(callbacks) { callbacks.add(callback) } + synchronized(callbacks) { + callbacks.add(callback) + state?.let(callback::onStateChanged) + } } override fun removeCallback(callback: QSTile.Callback?) { diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt index e57a0fddc936..3f6c58d73346 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.screenrecord +import android.annotation.SuppressLint import android.app.Activity import android.app.PendingIntent import android.content.Intent @@ -23,6 +24,7 @@ import android.os.Handler import android.os.Looper import android.os.ResultReceiver import android.os.UserHandle +import android.view.MotionEvent.ACTION_MOVE import android.view.View import android.view.View.GONE import android.view.View.VISIBLE @@ -102,11 +104,19 @@ class ScreenRecordPermissionDialogDelegate( @LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options + @SuppressLint("ClickableViewAccessibility") private fun initRecordOptionsView() { audioSwitch = dialog.requireViewById(R.id.screenrecord_audio_switch) tapsSwitch = dialog.requireViewById(R.id.screenrecord_taps_switch) + + // Add these listeners so that the switch only responds to movement + // within its target region, to meet accessibility requirements + audioSwitch.setOnTouchListener { _, event -> event.action == ACTION_MOVE } + tapsSwitch.setOnTouchListener { _, event -> event.action == ACTION_MOVE } + tapsView = dialog.requireViewById(R.id.show_taps) updateTapsViewVisibility() + options = dialog.requireViewById(R.id.screen_recording_options) val a: ArrayAdapter<*> = ScreenRecordingAdapter( diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index c06e9a443a7b..67ec03fc3d0a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1302,6 +1302,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void updateResources() { + Trace.beginSection("NSSLC#updateResources"); final boolean newSplitShadeEnabled = mSplitShadeStateController.shouldUseSplitNotificationShade(mResources); final boolean splitShadeChanged = mSplitShadeEnabled != newSplitShadeEnabled; @@ -1309,7 +1310,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mQsController.updateResources(); mNotificationsQSContainerController.updateResources(); updateKeyguardStatusViewAlignment(/* animate= */false); - mKeyguardMediaController.refreshMediaPosition(); + mKeyguardMediaController.refreshMediaPosition( + "NotificationPanelViewController.updateResources"); if (splitShadeChanged) { onSplitShadeEnabledChanged(); @@ -1317,6 +1319,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mSplitShadeFullTransitionDistance = mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance); + Trace.endSection(); } private void onSplitShadeEnabledChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 4a5089770b05..1b096b592a4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -29,8 +29,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.ResolveInfo; import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.input.InputManagerGlobal; @@ -119,7 +118,6 @@ public final class KeyboardShortcutListSearch { private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>(); private final SparseArray<String> mModifierNames = new SparseArray<>(); - private final SparseArray<Drawable> mSpecialCharacterDrawables = new SparseArray<>(); private final SparseArray<Drawable> mModifierDrawables = new SparseArray<>(); // Ordered list of modifiers that are supported. All values in this array must exist in // mModifierNames. @@ -146,7 +144,7 @@ public final class KeyboardShortcutListSearch { } else { this.mWindowManager = mContext.getSystemService(WindowManager.class); } - loadResources(context); + loadResources(this.mContext); createHardcodedShortcuts(); } @@ -287,7 +285,7 @@ public final class KeyboardShortcutListSearch { mSpecialCharacterNames.put( KeyEvent.KEYCODE_NUM_LOCK, context.getString(R.string.keyboard_key_num_lock)); mSpecialCharacterNames.put(KeyEvent.KEYCODE_MINUS, "-"); - mSpecialCharacterNames.put(KeyEvent.KEYCODE_GRAVE, "~"); + mSpecialCharacterNames.put(KeyEvent.KEYCODE_GRAVE, "`"); mSpecialCharacterNames.put(KeyEvent.KEYCODE_EQUALS, "="); mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_0, @@ -350,19 +348,6 @@ public final class KeyboardShortcutListSearch { mModifierNames.put(KeyEvent.META_SYM_ON, "Sym"); mModifierNames.put(KeyEvent.META_FUNCTION_ON, "Fn"); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_DEL, context.getDrawable(R.drawable.ic_ksh_key_backspace)); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_ENTER, context.getDrawable(R.drawable.ic_ksh_key_enter)); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_DPAD_UP, context.getDrawable(R.drawable.ic_ksh_key_up)); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_DPAD_RIGHT, context.getDrawable(R.drawable.ic_ksh_key_right)); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_DPAD_DOWN, context.getDrawable(R.drawable.ic_ksh_key_down)); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_DPAD_LEFT, context.getDrawable(R.drawable.ic_ksh_key_left)); - mModifierDrawables.put( KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta)); } @@ -508,7 +493,7 @@ public final class KeyboardShortcutListSearch { Arrays.asList( Pair.create(KeyEvent.KEYCODE_SLASH, KeyEvent.META_META_ON))), /* Back: go back to previous state (back button) */ - /* Meta + ~, Meta + backspace, Meta + left arrow */ + /* Meta + Grave, Meta + backspace, Meta + left arrow */ new ShortcutKeyGroupMultiMappingInfo( context.getString(R.string.group_system_go_back), Arrays.asList( @@ -826,11 +811,12 @@ public final class KeyboardShortcutListSearch { new BottomSheetDialog(mContext); final View keyboardShortcutsView = inflater.inflate( R.layout.keyboard_shortcuts_search_view, null); + LinearLayout shortcutsContainer = keyboardShortcutsView.findViewById( + R.id.keyboard_shortcuts_container); mNoSearchResults = keyboardShortcutsView.findViewById(R.id.shortcut_search_no_result); mKeyboardShortcutsBottomSheetDialog.setContentView(keyboardShortcutsView); setButtonsDefaultStatus(keyboardShortcutsView); - populateKeyboardShortcutSearchList( - keyboardShortcutsView.findViewById(R.id.keyboard_shortcuts_container)); + populateKeyboardShortcutSearchList(shortcutsContainer); // Workaround for solve issue about dialog not full expanded when landscape. FrameLayout bottomSheet = (FrameLayout) @@ -880,9 +866,14 @@ public final class KeyboardShortcutListSearch { @Override public void afterTextChanged(Editable s) { mQueryString = s.toString(); - populateKeyboardShortcutSearchList( - keyboardShortcutsView.findViewById( - R.id.keyboard_shortcuts_container)); + populateKeyboardShortcutSearchList(shortcutsContainer); + if (mNoSearchResults.getVisibility() == View.VISIBLE) { + shortcutsContainer.setAccessibilityPaneTitle(mContext.getString( + R.string.keyboard_shortcut_search_list_no_result)); + } else if (mSearchEditText.getText().length() > 0) { + shortcutsContainer.setAccessibilityPaneTitle(mContext.getString( + R.string.keyboard_shortcut_a11y_show_search_results)); + } } @Override @@ -1034,16 +1025,32 @@ public final class KeyboardShortcutListSearch { StringDrawableContainer shortcutRepresentation = shortcutKeys.get(k); if (shortcutRepresentation.mDrawable != null) { ImageView shortcutKeyIconView = (ImageView) inflater.inflate( - R.layout.keyboard_shortcuts_key_new_icon_view, + R.layout.keyboard_shortcuts_key_icon_view, shortcutItemsContainer, false); - Bitmap bitmap = Bitmap.createBitmap(shortcutKeyIconItemHeightWidth, - shortcutKeyIconItemHeightWidth, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - shortcutRepresentation.mDrawable.setBounds(0, 0, canvas.getWidth(), - canvas.getHeight()); - shortcutRepresentation.mDrawable.draw(canvas); - shortcutKeyIconView.setImageBitmap(bitmap); + shortcutKeyIconView.setImageDrawable( + shortcutRepresentation.mDrawable); + // Once the view has been measured, scale and position the icon in + // the center. + shortcutKeyIconView.post(() -> { + Drawable d = shortcutKeyIconView.getDrawable(); + + float newSize = mContext.getResources().getDimensionPixelSize( + R.dimen.ksh_icon_scaled_size); + int viewWidth = shortcutKeyIconView.getWidth(); + int viewHeight = shortcutKeyIconView.getHeight(); + float scaleFactor = newSize / d.getIntrinsicWidth(); + // Assumes that top/bottom and left/right padding are equal. + int paddingHorizontal = shortcutKeyIconView.getPaddingLeft(); + int paddingVertical = shortcutKeyIconView.getPaddingTop(); + + Matrix m = new Matrix(); + m.postScale(scaleFactor, scaleFactor); + m.postTranslate( + (viewWidth - newSize) / 2 - paddingHorizontal, + (viewHeight - newSize) / 2 - paddingVertical); + shortcutKeyIconView.setImageMatrix(m); + }); shortcutKeyIconView.setImportantForAccessibility( IMPORTANT_FOR_ACCESSIBILITY_YES); shortcutKeyIconView.setAccessibilityDelegate( @@ -1052,7 +1059,7 @@ public final class KeyboardShortcutListSearch { shortcutItemsContainer.addView(shortcutKeyIconView); } else if (shortcutRepresentation.mString != null) { TextView shortcutKeyTextView = (TextView) inflater.inflate( - R.layout.keyboard_shortcuts_key_new_view, + R.layout.keyboard_shortcuts_key_view, shortcutItemsContainer, false); shortcutKeyTextView.setMinimumWidth(shortcutKeyTextItemMinWidth); @@ -1062,18 +1069,10 @@ public final class KeyboardShortcutListSearch { shortcutRepresentation.mString)); shortcutItemsContainer.addView(shortcutKeyTextView); } - - if (k < shortcutKeysSize - 1) { - TextView shortcutKeyTextView = (TextView) inflater.inflate( - R.layout.keyboard_shortcuts_key_plus_view, - shortcutItemsContainer, - false); - shortcutItemsContainer.addView(shortcutKeyTextView); - } } } else { TextView shortcutKeyTextView = (TextView) inflater.inflate( - R.layout.keyboard_shortcuts_key_new_view, + R.layout.keyboard_shortcuts_key_view, shortcutItemsContainer, false); shortcutKeyTextView.setMinimumWidth(shortcutKeyTextItemMinWidth); @@ -1085,7 +1084,7 @@ public final class KeyboardShortcutListSearch { if (p < keyGroupItemsSize - 1) { TextView shortcutKeyTextView = (TextView) inflater.inflate( - R.layout.keyboard_shortcuts_key_vertical_bar_view, + R.layout.keyboard_shortcuts_key_separator_view, shortcutItemsContainer, false); shortcutItemsContainer.addView(shortcutKeyTextView); @@ -1124,9 +1123,6 @@ public final class KeyboardShortcutListSearch { Drawable shortcutKeyDrawable = null; if (info.getBaseCharacter() > Character.MIN_VALUE) { shortcutKeyString = String.valueOf(info.getBaseCharacter()); - } else if (mSpecialCharacterDrawables.get(info.getKeycode()) != null) { - shortcutKeyDrawable = mSpecialCharacterDrawables.get(info.getKeycode()); - shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode()); } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) { shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode()); } else { @@ -1232,28 +1228,35 @@ public final class KeyboardShortcutListSearch { mButtonOpenApps = keyboardShortcutsView.findViewById(R.id.shortcut_open_apps); mButtonSpecificApp = keyboardShortcutsView.findViewById(R.id.shortcut_specific_app); + LinearLayout shortcutsContainer = keyboardShortcutsView.findViewById( + R.id.keyboard_shortcuts_container); + mButtonSystem.setOnClickListener(v -> { setCurrentCategoryIndex(SHORTCUT_SYSTEM_INDEX); - populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById( - R.id.keyboard_shortcuts_container)); + populateKeyboardShortcutSearchList(shortcutsContainer); + shortcutsContainer.setAccessibilityPaneTitle(mContext.getString( + R.string.keyboard_shortcut_a11y_filter_system)); }); mButtonInput.setOnClickListener(v -> { setCurrentCategoryIndex(SHORTCUT_INPUT_INDEX); - populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById( - R.id.keyboard_shortcuts_container)); + populateKeyboardShortcutSearchList(shortcutsContainer); + shortcutsContainer.setAccessibilityPaneTitle(mContext.getString( + R.string.keyboard_shortcut_a11y_filter_input)); }); mButtonOpenApps.setOnClickListener(v -> { setCurrentCategoryIndex(SHORTCUT_OPENAPPS_INDEX); - populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById( - R.id.keyboard_shortcuts_container)); + populateKeyboardShortcutSearchList(shortcutsContainer); + shortcutsContainer.setAccessibilityPaneTitle(mContext.getString( + R.string.keyboard_shortcut_a11y_filter_open_apps)); }); mButtonSpecificApp.setOnClickListener(v -> { setCurrentCategoryIndex(SHORTCUT_SPECIFICAPP_INDEX); - populateKeyboardShortcutSearchList(keyboardShortcutsView.findViewById( - R.id.keyboard_shortcuts_container)); + populateKeyboardShortcutSearchList(shortcutsContainer); + shortcutsContainer.setAccessibilityPaneTitle(mContext.getString( + R.string.keyboard_shortcut_a11y_filter_current_app)); }); mFullButtonList.add(mButtonSystem); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index 61eaff9c2f8e..acb00d5016f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -85,7 +85,6 @@ public final class KeyboardShortcuts { private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>(); private final SparseArray<String> mModifierNames = new SparseArray<>(); - private final SparseArray<Drawable> mSpecialCharacterDrawables = new SparseArray<>(); private final SparseArray<Drawable> mModifierDrawables = new SparseArray<>(); // Ordered list of modifiers that are supported. All values in this array must exist in // mModifierNames. @@ -340,19 +339,6 @@ public final class KeyboardShortcuts { mModifierNames.put(KeyEvent.META_SYM_ON, "Sym"); mModifierNames.put(KeyEvent.META_FUNCTION_ON, "Fn"); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_DEL, context.getDrawable(R.drawable.ic_ksh_key_backspace)); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_ENTER, context.getDrawable(R.drawable.ic_ksh_key_enter)); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_DPAD_UP, context.getDrawable(R.drawable.ic_ksh_key_up)); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_DPAD_RIGHT, context.getDrawable(R.drawable.ic_ksh_key_right)); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_DPAD_DOWN, context.getDrawable(R.drawable.ic_ksh_key_down)); - mSpecialCharacterDrawables.put( - KeyEvent.KEYCODE_DPAD_LEFT, context.getDrawable(R.drawable.ic_ksh_key_left)); - mModifierDrawables.put( KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta)); } @@ -747,9 +733,6 @@ public final class KeyboardShortcuts { Drawable shortcutKeyDrawable = null; if (info.getBaseCharacter() > Character.MIN_VALUE) { shortcutKeyString = String.valueOf(info.getBaseCharacter()); - } else if (mSpecialCharacterDrawables.get(info.getKeycode()) != null) { - shortcutKeyDrawable = mSpecialCharacterDrawables.get(info.getKeycode()); - shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode()); } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) { shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode()); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt index b46b525f776c..a3adea0b86d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt @@ -16,8 +16,11 @@ package com.android.systemui.statusbar.connectivity +import android.os.UserManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.AirplaneModeTile import com.android.systemui.qs.tiles.BluetoothTile @@ -27,6 +30,16 @@ import com.android.systemui.qs.tiles.HotspotTile import com.android.systemui.qs.tiles.InternetTile import com.android.systemui.qs.tiles.InternetTileNewImpl import com.android.systemui.qs.tiles.NfcTile +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper +import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor +import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.res.R import dagger.Binds import dagger.Module import dagger.Provides @@ -70,6 +83,9 @@ interface ConnectivityModule { @Binds @IntoMap @StringKey(NfcTile.TILE_SPEC) fun bindNfcTile(nfcTile: NfcTile): QSTileImpl<*> companion object { + + const val AIRPLANE_MODE_TILE_SPEC = "airplane" + /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */ @Provides @IntoMap @@ -84,5 +100,37 @@ interface ConnectivityModule { } else { internetTile } + + @Provides + @IntoMap + @StringKey(AIRPLANE_MODE_TILE_SPEC) + fun provideAirplaneModeTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(AIRPLANE_MODE_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_airplane_icon_off, + labelRes = R.string.airplane_mode, + ), + instanceId = uiEventLogger.getNewInstanceId(), + policy = QSTilePolicy.Restricted(UserManager.DISALLOW_AIRPLANE_MODE), + ) + + /** Inject AirplaneModeTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(AIRPLANE_MODE_TILE_SPEC) + fun provideAirplaneModeTileViewModel( + factory: QSTileViewModelFactory.Static<AirplaneModeTileModel>, + mapper: AirplaneModeMapper, + stateInteractor: AirplaneModeTileDataInteractor, + userActionInteractor: AirplaneModeTileUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(AIRPLANE_MODE_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) } } 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 0299114e0afc..e0eee96d8f0a 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 @@ -34,34 +34,38 @@ object FooterViewBinder { viewModel: FooterViewModel, clearAllNotifications: View.OnClickListener, ): DisposableHandle { - // Listen for changes when the view is attached. + // Bind the resource IDs + footer.setMessageString(viewModel.message.messageId) + footer.setMessageIcon(viewModel.message.iconId) + footer.setClearAllButtonText(viewModel.clearAllButton.labelId) + footer.setClearAllButtonDescription(viewModel.clearAllButton.accessibilityDescriptionId) + + // Bind the click listeners + footer.setClearAllButtonClickListener(clearAllNotifications) + + // Listen for visibility changes when the view is attached. return footer.repeatWhenAttached { lifecycleScope.launch { - viewModel.clearAllButton.collect { button -> - if (button.isVisible.isAnimating) { + viewModel.clearAllButton.isVisible.collect { isVisible -> + if (isVisible.isAnimating) { footer.setClearAllButtonVisible( - button.isVisible.value, + isVisible.value, /* animate = */ true, ) { _ -> - button.isVisible.stopAnimating() + isVisible.stopAnimating() } } else { footer.setClearAllButtonVisible( - button.isVisible.value, + 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) - footer.setMessageString(message.messageId) - footer.setMessageIcon(message.iconId) + viewModel.message.isVisible.collect { visible -> + footer.setFooterLabelVisible(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 index ea5abeff7042..244555a3d73b 100644 --- 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 @@ -18,9 +18,10 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel import android.annotation.StringRes import com.android.systemui.util.ui.AnimatedValue +import kotlinx.coroutines.flow.Flow data class FooterButtonViewModel( @StringRes val labelId: Int, @StringRes val accessibilityDescriptionId: Int, - val isVisible: AnimatedValue<Boolean>, + val isVisible: Flow<AnimatedValue<Boolean>>, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt index bc912fb106f0..85cd397a3749 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt @@ -18,10 +18,11 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel import android.annotation.DrawableRes import android.annotation.StringRes +import kotlinx.coroutines.flow.StateFlow /** A ViewModel for the string message that can be shown in the footer. */ data class FooterMessageViewModel( @StringRes val messageId: Int, @DrawableRes val iconId: Int, - val visible: Boolean, + val isVisible: StateFlow<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 721bea1086e6..e6b0abcfad65 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 @@ -30,9 +30,7 @@ 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]. */ @@ -41,36 +39,32 @@ class FooterViewModel( 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 clearAllButton: FooterButtonViewModel = + FooterButtonViewModel( + labelId = R.string.clear_all_notifications_text, + accessibilityDescriptionId = R.string.accessibility_clear_all, + isVisible = + 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(), + ) - val message: Flow<FooterMessageViewModel> = - seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs -> - FooterMessageViewModel( - messageId = R.string.unlock_to_see_notif_text, - iconId = R.drawable.ic_friction_lock_closed, - visible = hasFilteredOutNotifs, - ) - } + val message: FooterMessageViewModel = + FooterMessageViewModel( + messageId = R.string.unlock_to_see_notif_text, + iconId = R.drawable.ic_friction_lock_closed, + isVisible = seenNotificationsInteractor.hasFilteredOutSeenNotifications, + ) } @Module 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 64f61d9ac2da..8eda96f62257 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,7 +27,6 @@ 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; @@ -43,11 +42,9 @@ 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; @@ -134,7 +131,6 @@ public class NotificationBackgroundView extends View implements Dumpable { unscheduleDrawable(mBackground); } mBackground = background; - mBackgroundDrawableToTint = findBackgroundDrawableToTint(mBackground); mRippleColor = null; mBackground.mutate(); if (mBackground != null) { @@ -148,46 +144,25 @@ 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) { - mBackgroundDrawableToTint.setTint(tintColor); - mBackgroundDrawableToTint.setTintMode(PorterDuff.Mode.SRC_ATOP); - + 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); + } mTintColor = tintColor; invalidate(); } 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 6944453506a8..3bbdfd164ba7 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 @@ -1275,6 +1275,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. */ private void updateChildren() { + Trace.beginSection("NSSL#updateChildren"); updateScrollStateForAddedChildren(); mAmbientState.setCurrentScrollVelocity(mScroller.isFinished() ? 0 @@ -1285,6 +1286,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } else { startAnimationToState(); } + Trace.endSection(); } private void onPreDrawDuringAnimation() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt index 7c10663bbb50..abf09ae9844c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt @@ -17,14 +17,14 @@ package com.android.systemui.statusbar.notification.stack.data.repository -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow -/** A repository which holds state about and controlling the appearance of the NSSL */ +/** A repository which holds state about and controlling the appearance of the notification stack */ @SysUISingleton class NotificationStackAppearanceRepository @Inject constructor() { - /** The position of the notification stack in the current scene */ - val stackPosition = MutableStateFlow(SharedNotificationContainerPosition(0f, 0f)) + /** The bounds of the notification stack in the current scene. */ + val stackBounds = MutableStateFlow(NotificationContainerBounds(0f, 0f)) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 820fe0b1f11e..32e4e89c42c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository import javax.inject.Inject @@ -31,13 +31,12 @@ class NotificationStackAppearanceInteractor constructor( private val repository: NotificationStackAppearanceRepository, ) { - /** The position of the notification stack in the current scene */ - val stackPosition: StateFlow<SharedNotificationContainerPosition> - get() = repository.stackPosition.asStateFlow() + /** The bounds of the notification stack in the current scene. */ + val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow() - /** Sets the position of the notification stack in the current scene */ - fun setStackPosition(position: SharedNotificationContainerPosition) { - check(position.top <= position.bottom) { "Invalid position: $position" } - repository.stackPosition.value = position + /** Sets the position of the notification stack in the current scene. */ + fun setStackBounds(bounds: NotificationContainerBounds) { + check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } + repository.stackBounds.value = bounds } } 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 4554085c35c0..a4e1a9c502f3 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 @@ -24,10 +24,12 @@ 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.dagger.qualifiers.Background import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.NotificationShelf +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore @@ -40,6 +42,7 @@ 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.CoroutineDispatcher import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -48,12 +51,13 @@ class NotificationListViewBinder @Inject constructor( private val viewModel: NotificationListViewModel, - private val metricsLogger: MetricsLogger, + @Background private val backgroundDispatcher: CoroutineDispatcher, private val configuration: ConfigurationState, private val configurationController: ConfigurationController, private val falsingManager: FalsingManager, private val iconAreaController: NotificationIconAreaController, private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, + private val metricsLogger: MetricsLogger, private val shelfIconViewStore: ShelfNotificationIconViewStore, ) { @@ -62,14 +66,17 @@ constructor( viewController: NotificationStackScrollLayoutController ) { bindShelf(view) - bindFooter(view) - bindEmptyShade(view) bindHideList(viewController, viewModel) - view.repeatWhenAttached { - lifecycleScope.launch { - viewModel.isImportantForAccessibility.collect { isImportantForAccessibility -> - view.setImportantForAccessibilityYesNo(isImportantForAccessibility) + if (FooterViewRefactor.isEnabled) { + bindFooter(view) + bindEmptyShade(view) + + view.repeatWhenAttached { + lifecycleScope.launch { + viewModel.isImportantForAccessibility.collect { isImportantForAccessibility -> + view.setImportantForAccessibilityYesNo(isImportantForAccessibility) + } } } } @@ -101,6 +108,7 @@ constructor( R.layout.status_bar_notification_footer, parentView, attachToRoot = false, + backgroundDispatcher, ) { footerView: FooterView -> traceSection("bind FooterView") { val disposableHandle = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt index 4d6a6ee98b7d..fa7a8fdb7495 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt @@ -40,17 +40,17 @@ object NotificationStackAppearanceViewBinder { view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { - viewModel.stackPosition.collect { + viewModel.stackBounds.collect { bounds -> controller.updateTopPadding( - it.top, + bounds.top, controller.isAddOrRemoveAnimationPending ) } } launch { - viewModel.expandFraction.collect { - ambientState.expansionFraction = it - controller.expandedHeight = it * controller.view.height + viewModel.expandFraction.collect { expandFraction -> + ambientState.expansionFraction = expandFraction + controller.expandedHeight = expandFraction * controller.view.height } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index 44006fc3fd4e..5e60b5f4c707 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -73,8 +73,9 @@ object SharedNotificationContainerBinder { if (!sceneContainerFlags.flexiNotifsEnabled()) { launch { - viewModel.position.collect { - val animate = it.animate || controller.isAddOrRemoveAnimationPending + viewModel.bounds.collect { + val animate = + it.isAnimated || controller.isAddOrRemoveAnimationPending controller.updateTopPadding(it.top, animate) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt index b86993486097..f4c0e92b0e87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor @@ -32,10 +32,9 @@ constructor( stackAppearanceInteractor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, ) { - /** The expansion fraction from the top of the notification shade */ + /** The expansion fraction from the top of the notification shade. */ val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion - /** The position of the notification stack in the current scene */ - val stackPosition: Flow<SharedNotificationContainerPosition> = - stackAppearanceInteractor.stackPosition + /** The bounds of the notification stack in the current scene. */ + val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 7def6feedb41..c6fd98ea2223 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags @@ -46,8 +46,18 @@ constructor( /** DEBUG: whether the debug logging should be output. */ val isDebugLoggingEnabled: Boolean = flags.flexiNotifsEnabled() - /** Sets the position of the notification stack in the current scene */ - fun setPlaceholderPositionInWindow(top: Float, bottom: Float) { - interactor.setStackPosition(SharedNotificationContainerPosition(top, bottom)) + /** + * Notifies that the bounds of the notification placeholder have changed. + * + * @param top The position of the top of the container in its window coordinate system, in + * pixels. + * @param bottom The position of the bottom of the container in its window coordinate system, in + * pixels. + */ + fun onBoundsChanged( + top: Float, + bottom: Float, + ) { + interactor.setStackBounds(NotificationContainerBounds(top, bottom)) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 09b4dfa31788..1febaf99b7b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -15,9 +15,11 @@ * */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -27,6 +29,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Share import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -35,7 +38,6 @@ import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -109,18 +111,18 @@ constructor( * * When the shade is expanding, the position is controlled by... the shade. */ - val position: StateFlow<SharedNotificationContainerPosition> = + val bounds: StateFlow<NotificationContainerBounds> = isOnLockscreenWithoutShade .flatMapLatest { onLockscreen -> if (onLockscreen) { combine( - keyguardInteractor.sharedNotificationContainerPosition, + keyguardInteractor.notificationContainerBounds, configurationBasedDimensions - ) { position, config -> + ) { bounds, config -> if (config.useSplitShade) { - position.copy(top = 0f) + bounds.copy(top = 0f) } else { - position + bounds } } } else { @@ -129,9 +131,9 @@ constructor( // When QS expansion > 0, it should directly set the top padding so do not // animate it val animate = qsExpansion == 0f - keyguardInteractor.sharedNotificationContainerPosition.value.copy( + keyguardInteractor.notificationContainerBounds.value.copy( top = top, - animate = animate + isAnimated = animate ) } } @@ -139,7 +141,7 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = SharedNotificationContainerPosition(0f, 0f), + initialValue = NotificationContainerBounds(0f, 0f), ) /** @@ -169,7 +171,7 @@ constructor( // when the notification stack has changed internally val limitedNotifications = combine( - position, + bounds, interactor.notificationStackChanged.onStart { emit(Unit) }, ) { position, _ -> calculateSpace(position.bottom - position.top) 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 674f1698d2a3..a3d316b611a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -22,6 +22,7 @@ import android.content.pm.PackageManager import android.hardware.biometrics.BiometricSourceType import android.provider.Settings import androidx.annotation.VisibleForTesting +import com.android.app.tracing.ListenersTracing.forEachTraced import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -186,7 +187,9 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr } } - private fun notifyListeners() = listeners.forEach { it.onBypassStateChanged(bypassEnabled) } + private fun notifyListeners() = listeners.forEachTraced("KeyguardBypassController") { + it.onBypassStateChanged(bypassEnabled) + } /** * Notify that the biometric unlock has happened. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java index a32a5ab13748..8f1ac812da71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java @@ -22,11 +22,14 @@ import android.util.SparseIntArray; import androidx.annotation.NonNull; +import com.android.app.tracing.ListenersTracing; import com.android.internal.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.util.Assert; +import kotlin.Unit; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -75,7 +78,11 @@ public class DevicePostureControllerImpl implements DevicePostureController { mCurrentDevicePosture = mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); - mListeners.forEach(l -> l.onPostureChanged(mCurrentDevicePosture)); + ListenersTracing.INSTANCE.forEachTraced(mListeners, "DevicePostureControllerImpl", + l -> { + l.onPostureChanged(mCurrentDevicePosture); + return Unit.INSTANCE; + }); }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index 78f48bb511e5..75ae16ebab31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -30,6 +30,10 @@ import com.android.systemui.qs.tiles.impl.flashlight.domain.FlashlightMapper import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel +import com.android.systemui.qs.tiles.impl.location.domain.LocationTileMapper +import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor +import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel @@ -54,8 +58,9 @@ interface PolicyModule { companion object { const val FLASHLIGHT_TILE_SPEC = "flashlight" + const val LOCATION_TILE_SPEC = "location" - /** Inject config */ + /** Inject flashlight config */ @Provides @IntoMap @StringKey(FLASHLIGHT_TILE_SPEC) @@ -86,6 +91,38 @@ interface PolicyModule { stateInteractor, mapper, ) + + /** Inject location config */ + @Provides + @IntoMap + @StringKey(LOCATION_TILE_SPEC) + fun provideLocationTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(LOCATION_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_location_icon_off, + labelRes = R.string.quick_settings_location_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject LocationTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(LOCATION_TILE_SPEC) + fun provideLocationTileViewModel( + factory: QSTileViewModelFactory.Static<LocationTileModel>, + mapper: LocationTileMapper, + stateInteractor: LocationTileDataInteractor, + userActionInteractor: LocationTileUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(LOCATION_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) } /** Inject FlashlightTile into tileMap in QSModule */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index e4e9554e1ab7..b59878253aa8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -125,14 +125,19 @@ public class StatusBarWindowController { * is different. */ public void refreshStatusBarHeight() { - int heightFromConfig = SystemBarUtils.getStatusBarHeight(mContext); + Trace.beginSection("StatusBarWindowController#refreshStatusBarHeight"); + try { + int heightFromConfig = SystemBarUtils.getStatusBarHeight(mContext); - if (mBarHeight != heightFromConfig) { - mBarHeight = heightFromConfig; - apply(mCurrentState); - } + if (mBarHeight != heightFromConfig) { + mBarHeight = heightFromConfig; + apply(mCurrentState); + } - if (DEBUG) Log.v(TAG, "defineSlots"); + if (DEBUG) Log.v(TAG, "defineSlots"); + } finally { + Trace.endSection(); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 5a9f5d5a72d2..886fa70d715d 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -19,6 +19,7 @@ package com.android.systemui.theme; import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; +import static com.android.systemui.Flags.themeOverlayControllerWakefulnessDeprecation; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET; @@ -71,12 +72,15 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.monet.ColorScheme; import com.android.systemui.monet.Style; import com.android.systemui.monet.TonalPalette; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors; @@ -127,7 +131,6 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { private final SecureSettings mSecureSettings; private final Executor mMainExecutor; private final Handler mBgHandler; - private final boolean mIsMonochromaticEnabled; private final Context mContext; private final boolean mIsMonetEnabled; private final boolean mIsFidelityEnabled; @@ -161,6 +164,8 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { private final SparseArray<WallpaperColors> mDeferredWallpaperColors = new SparseArray<>(); private final SparseIntArray mDeferredWallpaperColorsFlags = new SparseIntArray(); private final WakefulnessLifecycle mWakefulnessLifecycle; + private final JavaAdapter mJavaAdapter; + private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final UiModeManager mUiModeManager; private DynamicScheme mDynamicSchemeDark; private DynamicScheme mDynamicSchemeLight; @@ -200,8 +205,12 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { return; } boolean currentUser = userId == mUserTracker.getUserId(); - if (currentUser && !mAcceptColorEvents - && mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP) { + boolean isAsleep = themeOverlayControllerWakefulnessDeprecation() + ? mKeyguardTransitionInteractor.isFinishedInStateWhereValue( + state -> KeyguardState.Companion.deviceIsAsleepInState(state)) + : mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP; + + if (currentUser && !mAcceptColorEvents && isAsleep) { mDeferredWallpaperColors.put(userId, wallpaperColors); mDeferredWallpaperColorsFlags.put(userId, which); Log.i(TAG, "colors received; processing deferred until screen off: " @@ -395,9 +404,10 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { FeatureFlags featureFlags, @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle, + JavaAdapter javaAdapter, + KeyguardTransitionInteractor keyguardTransitionInteractor, UiModeManager uiModeManager) { mContext = context; - mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME); mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET); mIsFidelityEnabled = featureFlags.isEnabled(Flags.COLOR_FIDELITY); mDeviceProvisionedController = deviceProvisionedController; @@ -412,6 +422,8 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { mUserTracker = userTracker; mResources = resources; mWakefulnessLifecycle = wakefulnessLifecycle; + mJavaAdapter = javaAdapter; + mKeyguardTransitionInteractor = keyguardTransitionInteractor; mUiModeManager = uiModeManager; dumpManager.registerDumpable(TAG, this); } @@ -494,21 +506,34 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { } mWallpaperManager.addOnColorsChangedListener(mOnColorsChangedListener, null, UserHandle.USER_ALL); - mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() { - @Override - public void onFinishedGoingToSleep() { - final int userId = mUserTracker.getUserId(); - final WallpaperColors colors = mDeferredWallpaperColors.get(userId); - if (colors != null) { - int flags = mDeferredWallpaperColorsFlags.get(userId); - - mDeferredWallpaperColors.put(userId, null); - mDeferredWallpaperColorsFlags.put(userId, 0); - - handleWallpaperColors(colors, flags, userId); - } + + Runnable whenAsleepHandler = () -> { + final int userId = mUserTracker.getUserId(); + final WallpaperColors colors = mDeferredWallpaperColors.get(userId); + if (colors != null) { + int flags = mDeferredWallpaperColorsFlags.get(userId); + + mDeferredWallpaperColors.put(userId, null); + mDeferredWallpaperColorsFlags.put(userId, 0); + + handleWallpaperColors(colors, flags, userId); } - }); + }; + + if (themeOverlayControllerWakefulnessDeprecation()) { + mJavaAdapter.alwaysCollectFlow( + mKeyguardTransitionInteractor.isFinishedInState(KeyguardState.DOZING), + isFinishedInDozing -> { + if (isFinishedInDozing) whenAsleepHandler.run(); + }); + } else { + mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() { + @Override + public void onFinishedGoingToSleep() { + whenAsleepHandler.run(); + } + }); + } } private void reevaluateSystemTheme(boolean forceReload) { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt index 1482cfccdfc6..10fc83c8b82c 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt @@ -20,11 +20,13 @@ import com.android.keyguard.KeyguardUnfoldTransition import com.android.systemui.dagger.SysUISingleton import com.android.systemui.shade.NotificationPanelUnfoldAnimationController import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController -import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager +import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider +import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager import com.android.systemui.util.kotlin.getOrNull import dagger.BindsInstance +import dagger.Lazy import dagger.Module import dagger.Provides import dagger.Subcomponent @@ -32,10 +34,7 @@ import java.util.Optional import javax.inject.Named import javax.inject.Scope -@Scope -@MustBeDocumented -@Retention(AnnotationRetention.RUNTIME) -annotation class SysUIUnfoldScope +@Scope @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class SysUIUnfoldScope /** * Creates an injectable [SysUIUnfoldComponent] that provides objects that have been scoped with @@ -57,15 +56,18 @@ class SysUIUnfoldModule { provider: Optional<UnfoldTransitionProgressProvider>, rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>, @Named(UNFOLD_STATUS_BAR) scopedProvider: Optional<ScopedUnfoldTransitionProgressProvider>, + @UnfoldBg bgProvider: Optional<UnfoldTransitionProgressProvider>, + unfoldLatencyTracker: Lazy<UnfoldLatencyTracker>, factory: SysUIUnfoldComponent.Factory ): Optional<SysUIUnfoldComponent> { val p1 = provider.getOrNull() val p2 = rotationProvider.getOrNull() val p3 = scopedProvider.getOrNull() - return if (p1 == null || p2 == null || p3 == null) { + val p4 = bgProvider.getOrNull() + return if (p1 == null || p2 == null || p3 == null || p4 == null) { Optional.empty() } else { - Optional.of(factory.create(p1, p2, p3)) + Optional.of(factory.create(p1, p2, p3, p4, unfoldLatencyTracker.get())) } } } @@ -79,7 +81,9 @@ interface SysUIUnfoldComponent { fun create( @BindsInstance p1: UnfoldTransitionProgressProvider, @BindsInstance p2: NaturalRotationUnfoldProgressProvider, - @BindsInstance p3: ScopedUnfoldTransitionProgressProvider + @BindsInstance p3: ScopedUnfoldTransitionProgressProvider, + @BindsInstance @UnfoldBg p4: UnfoldTransitionProgressProvider, + @BindsInstance p5: UnfoldLatencyTracker, ): SysUIUnfoldComponent } @@ -98,4 +102,8 @@ interface SysUIUnfoldComponent { fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation fun getUnfoldKeyguardVisibilityManager(): UnfoldKeyguardVisibilityManager + + fun getUnfoldLatencyTracker(): UnfoldLatencyTracker + + fun getNaturalRotationUnfoldProgressProvider(): NaturalRotationUnfoldProgressProvider } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 36a1e8a072c9..b72c6f189e3e 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -35,6 +35,9 @@ import android.view.SurfaceControlViewHost import android.view.SurfaceSession import android.view.WindowManager import android.view.WindowlessWindowManager +import com.android.app.tracing.traceSection +import com.android.keyguard.logging.ScrimLogger +import com.android.systemui.Flags.unfoldAnimationBackgroundProgress import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -45,16 +48,16 @@ import com.android.systemui.statusbar.LinearLightRevealEffect import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled import com.android.systemui.util.concurrency.ThreadFactory -import com.android.app.tracing.traceSection -import com.android.keyguard.logging.ScrimLogger import com.android.wm.shell.displayareahelper.DisplayAreaHelper import java.util.Optional import java.util.concurrent.Executor import java.util.function.Consumer import javax.inject.Inject +import javax.inject.Provider @SysUIUnfoldScope class UnfoldLightRevealOverlayAnimation @@ -65,11 +68,14 @@ constructor( private val deviceStateManager: DeviceStateManager, private val contentResolver: ContentResolver, private val displayManager: DisplayManager, - private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, + @UnfoldBg + private val unfoldTransitionBgProgressProvider: Provider<UnfoldTransitionProgressProvider>, + private val unfoldTransitionProgressProvider: Provider<UnfoldTransitionProgressProvider>, private val displayAreaHelper: Optional<DisplayAreaHelper>, @Main private val executor: Executor, private val threadFactory: ThreadFactory, - private val rotationChangeProvider: RotationChangeProvider, + @UnfoldBg private val rotationChangeProvider: RotationChangeProvider, + @UnfoldBg private val unfoldProgressHandler: Handler, private val displayTracker: DisplayTracker, private val scrimLogger: ScrimLogger, ) { @@ -96,11 +102,15 @@ constructor( fun init() { // This method will be called only on devices where this animation is enabled, // so normally this thread won't be created - bgHandler = threadFactory.buildHandlerOnNewThread(TAG) + bgHandler = unfoldProgressHandler bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler) deviceStateManager.registerCallback(bgExecutor, FoldListener()) - unfoldTransitionProgressProvider.addCallback(transitionListener) + if (unfoldAnimationBackgroundProgress()) { + unfoldTransitionBgProgressProvider.get().addCallback(transitionListener) + } else { + unfoldTransitionProgressProvider.get().addCallback(transitionListener) + } rotationChangeProvider.addCallback(rotationWatcher) val containerBuilder = @@ -169,8 +179,13 @@ constructor( overlayAddReason = reason - val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, - "UnfoldLightRevealOverlayAnimation") + val newRoot = + SurfaceControlViewHost( + context, + context.display, + wwm, + "UnfoldLightRevealOverlayAnimation" + ) val params = getLayoutParams() val newView = LightRevealScrim( @@ -353,12 +368,13 @@ constructor( } private fun executeInBackground(f: () -> Unit) { - check(Looper.myLooper() != bgHandler.looper) { - "Trying to execute using background handler while already running" + - " in the background handler" + // This is needed to allow progresses to be received both from the main thread (that will + // schedule a runnable on the bg thread), and from the bg thread directly (no reposting). + if (bgHandler.looper.isCurrentThread) { + f() + } else { + bgHandler.post(f) } - // The UiBackground executor is not used as it doesn't have a prepared looper. - bgHandler.post(f) } private fun ensureInBackground() { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt index 12b88458d355..94912bf82377 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt @@ -21,11 +21,14 @@ import com.android.app.tracing.TraceStateLogger import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.unfold.system.DeviceStateRepository import com.android.systemui.unfold.updates.FoldStateRepository import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.plus /** * Logs several unfold related details in a trace. Mainly used for debugging and investigate @@ -37,7 +40,8 @@ class UnfoldTraceLogger constructor( private val context: Context, private val foldStateRepository: FoldStateRepository, - @Application private val applicationScope: CoroutineScope, + @Application applicationScope: CoroutineScope, + @Background private val coroutineContext: CoroutineContext, private val deviceStateRepository: DeviceStateRepository ) : CoreStartable { private val isFoldable: Boolean @@ -46,20 +50,22 @@ constructor( .getIntArray(com.android.internal.R.array.config_foldedDeviceStates) .isNotEmpty() + private val bgScope = applicationScope.plus(coroutineContext) + override fun start() { if (!isFoldable) return - applicationScope.launch { + bgScope.launch { val foldUpdateLogger = TraceStateLogger("FoldUpdate") foldStateRepository.foldUpdate.collect { foldUpdateLogger.log(it.name) } } - applicationScope.launch { + bgScope.launch { foldStateRepository.hingeAngle.collect { Trace.traceCounter(Trace.TRACE_TAG_APP, "hingeAngle", it.toInt()) } } - applicationScope.launch { + bgScope.launch { val foldedStateLogger = TraceStateLogger("FoldedState") deviceStateRepository.isFolded.collect { isFolded -> foldedStateLogger.log( diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index 7b628f8d676f..053148709e69 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -24,6 +24,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.LifecycleScreenStatusProvider import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor @@ -102,7 +103,7 @@ class UnfoldTransitionModule { @Singleton fun provideNaturalRotationProgressProvider( context: Context, - rotationChangeProvider: RotationChangeProvider, + @UnfoldMain rotationChangeProvider: RotationChangeProvider, unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> ): Optional<NaturalRotationUnfoldProgressProvider> = unfoldTransitionProgressProvider.map { provider -> @@ -153,7 +154,8 @@ class UnfoldTransitionModule { return resultingProvider?.get()?.orElse(null)?.let { unfoldProgressProvider -> UnfoldProgressProvider(unfoldProgressProvider, foldProvider) - } ?: ShellUnfoldProgressProvider.NO_PROVIDER + } + ?: ShellUnfoldProgressProvider.NO_PROVIDER } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapper.kt index a2a44e46919f..b2297d0d5821 100644 --- a/packages/SystemUI/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapper.kt @@ -30,14 +30,16 @@ class LoopedAnimatable2DrawableWrapper private constructor(private val animatabl private val loopedCallback = LoopedCallback() + private var isLoopedCallbackRegistered: Boolean = false + override fun start() { animatable2.start() - animatable2.registerAnimationCallback(loopedCallback) + setLoopingRegistered(true) } override fun stop() { // stop looping if someone stops the animation - animatable2.unregisterAnimationCallback(loopedCallback) + setLoopingRegistered(false) animatable2.stop() } @@ -49,7 +51,25 @@ class LoopedAnimatable2DrawableWrapper private constructor(private val animatabl override fun unregisterAnimationCallback(callback: Animatable2.AnimationCallback): Boolean = animatable2.unregisterAnimationCallback(callback) - override fun clearAnimationCallbacks() = animatable2.clearAnimationCallbacks() + override fun clearAnimationCallbacks() { + animatable2.clearAnimationCallbacks() + // re-register looped callback to maintain looped behaviour. LoopedCallback is a static + // class and it has no extra references, so it doesn't provoke a memory leak. + isLoopedCallbackRegistered = false + setLoopingRegistered(true) + } + + private fun setLoopingRegistered(isLooping: Boolean) { + if (isLooping == isLoopedCallbackRegistered) { + return + } + isLoopedCallbackRegistered = isLooping + if (isLooping) { + animatable2.registerAnimationCallback(loopedCallback) + } else { + animatable2.unregisterAnimationCallback(loopedCallback) + } + } override fun getConstantState(): ConstantState? = drawable!!.constantState?.let(LoopedAnimatable2DrawableWrapper::LoopedDrawableState) diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt index cc9335edfc14..472f0ae364c5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.plus import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -29,6 +30,14 @@ class CoroutinesModule { @Provides @SysUISingleton + @Background + fun bgApplicationScope( + @Application applicationScope: CoroutineScope, + @Background coroutineContext: CoroutineContext, + ): CoroutineScope = applicationScope.plus(coroutineContext) + + @Provides + @SysUISingleton @Main @Deprecated( "Use @Main CoroutineContext instead", diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt index c825d2ea65ad..834179bf289d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt @@ -38,6 +38,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -97,7 +98,8 @@ class DisplayStateRepositoryTest : SysuiTestCase() { deviceStateManager, displayManager, handler, - fakeExecutor + fakeExecutor, + UnconfinedTestDispatcher(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt index 0d44ed30431f..f0d26b6bbb78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt @@ -67,6 +67,13 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { ) biometricSettingsRepository = FakeBiometricSettingsRepository() fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + + mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + initializeUnderTest() + } + + private fun initializeUnderTest() { + // Set any feature flags before creating the alternateBouncerInteractor underTest = AlternateBouncerInteractor( statusBarStateController, @@ -161,6 +168,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @Test fun canShowAlternateBouncerForFingerprint_rearFps() { mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + initializeUnderTest() givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsRearFps() // does not support alternate bouncer @@ -169,7 +177,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { @Test fun alternateBouncerUiAvailable_fromMultipleSources() { - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + initializeUnderTest() assertFalse(bouncerRepository.alternateBouncerUIAvailable.value) // GIVEN there are two different sources indicating the alternate bouncer is available diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt index 034b8022305a..112cec25784c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt @@ -30,6 +30,8 @@ import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -44,102 +46,112 @@ class ConfigurationStateTest : SysuiTestCase() { private val configurationController: ConfigurationController = mock() private val layoutInflater = TestLayoutInflater() + private val backgroundDispatcher = StandardTestDispatcher() + private val testScope = TestScope(backgroundDispatcher) val underTest = ConfigurationState(configurationController, context, layoutInflater) @Test - fun reinflateAndBindLatest_inflatesWithoutEmission() = runTest { - var callbackCount = 0 - backgroundScope.launch { - underTest.reinflateAndBindLatest<View>( - resource = 0, - root = null, - attachToRoot = false, - ) { - callbackCount++ - null + fun reinflateAndBindLatest_inflatesWithoutEmission() = + testScope.runTest { + var callbackCount = 0 + backgroundScope.launch { + underTest.reinflateAndBindLatest<View>( + resource = 0, + root = null, + attachToRoot = false, + backgroundDispatcher, + ) { + callbackCount++ + null + } } - } - // Inflates without an emission - runCurrent() - assertThat(layoutInflater.inflationCount).isEqualTo(1) - assertThat(callbackCount).isEqualTo(1) - } + // Inflates without an emission + runCurrent() + assertThat(layoutInflater.inflationCount).isEqualTo(1) + assertThat(callbackCount).isEqualTo(1) + } @Test - fun reinflateAndBindLatest_reinflatesOnThemeChanged() = runTest { - var callbackCount = 0 - backgroundScope.launch { - underTest.reinflateAndBindLatest<View>( - resource = 0, - root = null, - attachToRoot = false, - ) { - callbackCount++ - null + fun reinflateAndBindLatest_reinflatesOnThemeChanged() = + testScope.runTest { + var callbackCount = 0 + backgroundScope.launch { + underTest.reinflateAndBindLatest<View>( + resource = 0, + root = null, + attachToRoot = false, + backgroundDispatcher, + ) { + callbackCount++ + null + } } - } - runCurrent() + runCurrent() - val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany { - verify(configurationController, atLeastOnce()).addCallback(capture()) - } + val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany { + verify(configurationController, atLeastOnce()).addCallback(capture()) + } - listOf(1, 2, 3).forEach { count -> - assertThat(layoutInflater.inflationCount).isEqualTo(count) - assertThat(callbackCount).isEqualTo(count) - configListeners.forEach { it.onThemeChanged() } - runCurrent() + listOf(1, 2, 3).forEach { count -> + assertThat(layoutInflater.inflationCount).isEqualTo(count) + assertThat(callbackCount).isEqualTo(count) + configListeners.forEach { it.onThemeChanged() } + runCurrent() + } } - } @Test - fun reinflateAndBindLatest_reinflatesOnDensityOrFontScaleChanged() = runTest { - var callbackCount = 0 - backgroundScope.launch { - underTest.reinflateAndBindLatest<View>( - resource = 0, - root = null, - attachToRoot = false, - ) { - callbackCount++ - null + fun reinflateAndBindLatest_reinflatesOnDensityOrFontScaleChanged() = + testScope.runTest { + var callbackCount = 0 + backgroundScope.launch { + underTest.reinflateAndBindLatest<View>( + resource = 0, + root = null, + attachToRoot = false, + backgroundDispatcher, + ) { + callbackCount++ + null + } } - } - runCurrent() + runCurrent() - val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany { - verify(configurationController, atLeastOnce()).addCallback(capture()) - } + val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany { + verify(configurationController, atLeastOnce()).addCallback(capture()) + } - listOf(1, 2, 3).forEach { count -> - assertThat(layoutInflater.inflationCount).isEqualTo(count) - assertThat(callbackCount).isEqualTo(count) - configListeners.forEach { it.onDensityOrFontScaleChanged() } - runCurrent() + listOf(1, 2, 3).forEach { count -> + assertThat(layoutInflater.inflationCount).isEqualTo(count) + assertThat(callbackCount).isEqualTo(count) + configListeners.forEach { it.onDensityOrFontScaleChanged() } + runCurrent() + } } - } @Test - fun testReinflateAndBindLatest_disposesOnCancel() = runTest { - var callbackCount = 0 - var disposed = false - val job = launch { - underTest.reinflateAndBindLatest<View>( - resource = 0, - root = null, - attachToRoot = false, - ) { - callbackCount++ - DisposableHandle { disposed = true } + fun testReinflateAndBindLatest_disposesOnCancel() = + testScope.runTest { + var callbackCount = 0 + var disposed = false + val job = launch { + underTest.reinflateAndBindLatest<View>( + resource = 0, + root = null, + attachToRoot = false, + backgroundDispatcher, + ) { + callbackCount++ + DisposableHandle { disposed = true } + } } - } - runCurrent() - job.cancelAndJoin() - assertThat(disposed).isTrue() - } + runCurrent() + job.cancelAndJoin() + assertThat(disposed).isTrue() + } inner class TestLayoutInflater : LayoutInflater(context) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt index a58bc52bf8e5..2b7221ec192c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -69,6 +70,7 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { authController, keyguardUpdateMonitor, testScope.backgroundScope, + UnconfinedTestDispatcher(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt index 9be5558f5cc2..ae6c5b7b36b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -49,7 +50,11 @@ class DevicePostureRepositoryTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) testScope = TestScope() - underTest = DevicePostureRepositoryImpl(postureController = devicePostureController) + underTest = + DevicePostureRepositoryImpl( + postureController = devicePostureController, + UnconfinedTestDispatcher() + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 15a17827a603..740fce988a68 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -30,7 +30,7 @@ import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.items.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection @@ -57,7 +57,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { private lateinit var underTest: DefaultKeyguardBlueprint private lateinit var rootView: KeyguardRootView @Mock private lateinit var defaultIndicationAreaSection: DefaultIndicationAreaSection - @Mock private lateinit var mDefaultDeviceEntryIconSection: DefaultDeviceEntryIconSection + @Mock private lateinit var mDefaultDeviceEntrySection: DefaultDeviceEntrySection @Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection @Mock private lateinit var defaultAmbientIndicationAreaSection: Optional<KeyguardSection> @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection @@ -78,7 +78,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { underTest = DefaultKeyguardBlueprint( defaultIndicationAreaSection, - mDefaultDeviceEntryIconSection, + mDefaultDeviceEntrySection, defaultShortcutsSection, defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, @@ -105,14 +105,14 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { val prevBlueprint = mock(KeyguardBlueprint::class.java) val someSection = mock(KeyguardSection::class.java) whenever(prevBlueprint.sections) - .thenReturn(underTest.sections.minus(mDefaultDeviceEntryIconSection).plus(someSection)) + .thenReturn(underTest.sections.minus(mDefaultDeviceEntrySection).plus(someSection)) val constraintLayout = ConstraintLayout(context, null) underTest.replaceViews(prevBlueprint, constraintLayout) - underTest.sections.minus(mDefaultDeviceEntryIconSection).forEach { + underTest.sections.minus(mDefaultDeviceEntrySection).forEach { verify(it, never())?.addViews(constraintLayout) } - verify(mDefaultDeviceEntryIconSection).addViews(constraintLayout) + verify(mDefaultDeviceEntrySection).addViews(constraintLayout) verify(someSection).removeViews(constraintLayout) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index d9760456bcef..67fba42aac5b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.biometrics.AuthController import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel @@ -38,6 +39,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.gesture.TapGestureDetector import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -53,7 +55,7 @@ import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi @RunWith(JUnit4::class) @SmallTest -class DefaultDeviceEntryIconSectionTest : SysuiTestCase() { +class DefaultDeviceEntrySectionTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var authController: AuthController @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager @@ -61,7 +63,7 @@ class DefaultDeviceEntryIconSectionTest : SysuiTestCase() { private lateinit var featureFlags: FakeFeatureFlags @Mock private lateinit var lockIconViewController: LockIconViewController @Mock private lateinit var falsingManager: FalsingManager - private lateinit var underTest: DefaultDeviceEntryIconSection + private lateinit var underTest: DefaultDeviceEntrySection @Before fun setup() { @@ -72,7 +74,7 @@ class DefaultDeviceEntryIconSectionTest : SysuiTestCase() { featureFlags = FakeFeatureFlagsClassic().apply { set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) } underTest = - DefaultDeviceEntryIconSection( + DefaultDeviceEntrySection( keyguardUpdateMonitor, authController, windowManager, @@ -87,6 +89,8 @@ class DefaultDeviceEntryIconSectionTest : SysuiTestCase() { { mock(AlternateBouncerViewModel::class.java) }, { mock(NotificationShadeWindowController::class.java) }, TestScope().backgroundScope, + { mock(SwipeUpAnywhereGestureHandler::class.java) }, + { mock(TapGestureDetector::class.java) }, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt index 1768f8c4385b..fc9f54ec74f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt @@ -27,7 +27,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac 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.plugins.FalsingManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat @@ -50,7 +49,6 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { private lateinit var testScope: TestScope @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager - @Mock private lateinit var falsingManager: FalsingManager private lateinit var transitionRepository: FakeKeyguardTransitionRepository private lateinit var transitionInteractor: KeyguardTransitionInteractor @@ -69,7 +67,6 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { AlternateBouncerViewModel( statusBarKeyguardViewManager, transitionInteractor, - falsingManager, ) } @@ -106,46 +103,37 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { } @Test - fun clickListenerUpdate() = + fun forcePluginOpen() = runTest(UnconfinedTestDispatcher()) { - val clickListener by collectLastValue(underTest.onClickListener) - - // keyguard state => ALTERNATE_BOUNCER + val forcePluginOpen by collectLastValue(underTest.forcePluginOpen) transitionRepository.sendTransitionStep( stepToAlternateBouncer(0f, TransitionState.STARTED) ) - assertThat(clickListener).isNull() transitionRepository.sendTransitionStep(stepToAlternateBouncer(.3f)) - assertThat(clickListener).isNull() transitionRepository.sendTransitionStep(stepToAlternateBouncer(.6f)) - assertThat(clickListener).isNull() transitionRepository.sendTransitionStep(stepToAlternateBouncer(1f)) - assertThat(clickListener).isNotNull() + assertThat(forcePluginOpen).isTrue() - // ALTERNATE_BOUNCER -> keyguard state transitionRepository.sendTransitionStep( stepFromAlternateBouncer(0f, TransitionState.STARTED) ) - assertThat(clickListener).isNotNull() transitionRepository.sendTransitionStep(stepFromAlternateBouncer(.3f)) - assertThat(clickListener).isNull() transitionRepository.sendTransitionStep(stepFromAlternateBouncer(.6f)) - assertThat(clickListener).isNull() transitionRepository.sendTransitionStep(stepFromAlternateBouncer(1f)) - assertThat(clickListener).isNull() + assertThat(forcePluginOpen).isFalse() } @Test - fun forcePluginOpen() = + fun registerForDismissGestures() = runTest(UnconfinedTestDispatcher()) { - val forcePluginOpen by collectLastValue(underTest.forcePluginOpen) + val registerForDismissGestures by collectLastValue(underTest.registerForDismissGestures) transitionRepository.sendTransitionStep( stepToAlternateBouncer(0f, TransitionState.STARTED) ) transitionRepository.sendTransitionStep(stepToAlternateBouncer(.3f)) transitionRepository.sendTransitionStep(stepToAlternateBouncer(.6f)) transitionRepository.sendTransitionStep(stepToAlternateBouncer(1f)) - assertThat(forcePluginOpen).isTrue() + assertThat(registerForDismissGestures).isTrue() transitionRepository.sendTransitionStep( stepFromAlternateBouncer(0f, TransitionState.STARTED) @@ -153,7 +141,7 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { transitionRepository.sendTransitionStep(stepFromAlternateBouncer(.3f)) transitionRepository.sendTransitionStep(stepFromAlternateBouncer(.6f)) transitionRepository.sendTransitionStep(stepFromAlternateBouncer(1f)) - assertThat(forcePluginOpen).isFalse() + assertThat(registerForDismissGestures).isFalse() } private fun stepToAlternateBouncer( 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 baac51385ba1..ba72b4c95a44 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 @@ -14,83 +14,56 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.TestMocksModule -import com.android.systemui.collectLastValue -import com.android.systemui.collectValues -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues 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.flags.featureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - UserDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<LockscreenToDreamingTransitionViewModel> { - val repository: FakeKeyguardTransitionRepository - val keyguardRepository: FakeKeyguardRepository - val shadeRepository: FakeShadeRepository - - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - featureFlags: FakeFeatureFlagsClassicModule, - mocks: TestMocksModule, - ): TestComponent - } - } - private fun TestComponent.shadeExpanded(expanded: Boolean) { - if (expanded) { - shadeRepository.setQsExpansion(1f) - } else { - keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - shadeRepository.setQsExpansion(0f) - shadeRepository.setLockscreenShadeExpansion(0f) + private val kosmos = + testKosmos().apply { + featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } - } + private val testScope = kosmos.testScope + private val repository = kosmos.fakeKeyguardTransitionRepository + private val shadeRepository = kosmos.shadeRepository + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val underTest = + LockscreenToDreamingTransitionViewModel( + interactor = kosmos.keyguardTransitionInteractor, + shadeDependentFlows = kosmos.shadeDependentFlows, + ) - private val testComponent: TestComponent = - DaggerLockscreenToDreamingTransitionViewModelTest_TestComponent.factory() - .create( - test = this, - featureFlags = - FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, - mocks = TestMocksModule(), - ) @Test fun lockscreenFadeOut() = - testComponent.runTest { + testScope.runTest { val values by collectValues(underTest.lockscreenAlpha) repository.sendTransitionSteps( steps = @@ -113,7 +86,7 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { @Test fun lockscreenTranslationY() = - testComponent.runTest { + testScope.runTest { val pixels = 100 val values by collectValues(underTest.lockscreenTranslationY(pixels)) @@ -138,7 +111,7 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { @Test fun deviceEntryParentViewAlpha_shadeExpanded() = - testComponent.runTest { + testScope.runTest { val values by collectValues(underTest.deviceEntryParentViewAlpha) shadeExpanded(true) runCurrent() @@ -162,7 +135,7 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { @Test fun deviceEntryParentViewAlpha_shadeNotExpanded() = - testComponent.runTest { + testScope.runTest { val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) shadeExpanded(false) runCurrent() @@ -194,4 +167,14 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { ownerName = "LockscreenToDreamingTransitionViewModelTest" ) } + + private fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } } 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 29d8f082795a..3536d5c77c93 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 @@ -14,84 +14,56 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.TestMocksModule -import com.android.systemui.collectLastValue -import com.android.systemui.collectValues -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues 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.flags.featureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - UserDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<LockscreenToOccludedTransitionViewModel> { - val repository: FakeKeyguardTransitionRepository - val keyguardRepository: FakeKeyguardRepository - val shadeRepository: FakeShadeRepository - - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - featureFlags: FakeFeatureFlagsClassicModule, - mocks: TestMocksModule, - ): TestComponent - } - } - private fun TestComponent.shadeExpanded(expanded: Boolean) { - if (expanded) { - shadeRepository.setQsExpansion(1f) - } else { - keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - shadeRepository.setQsExpansion(0f) - shadeRepository.setLockscreenShadeExpansion(0f) + private val kosmos = + testKosmos().apply { + featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } - } - - private val testComponent: TestComponent = - DaggerLockscreenToOccludedTransitionViewModelTest_TestComponent.factory() - .create( - test = this, - featureFlags = - FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, - mocks = TestMocksModule(), - ) + private val testScope = kosmos.testScope + private val repository = kosmos.fakeKeyguardTransitionRepository + private val shadeRepository = kosmos.shadeRepository + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val underTest = + LockscreenToOccludedTransitionViewModel( + interactor = kosmos.keyguardTransitionInteractor, + shadeDependentFlows = kosmos.shadeDependentFlows, + ) @Test fun lockscreenFadeOut() = - testComponent.runTest { + testScope.runTest { val values by collectValues(underTest.lockscreenAlpha) repository.sendTransitionSteps( steps = @@ -113,7 +85,7 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Test fun lockscreenTranslationY() = - testComponent.runTest { + testScope.runTest { val pixels = 100 val values by collectValues(underTest.lockscreenTranslationY(pixels)) repository.sendTransitionSteps( @@ -133,7 +105,7 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Test fun lockscreenTranslationYIsCanceled() = - testComponent.runTest { + testScope.runTest { val pixels = 100 val values by collectValues(underTest.lockscreenTranslationY(pixels)) repository.sendTransitionSteps( @@ -155,7 +127,7 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Test fun deviceEntryParentViewAlpha_shadeExpanded() = - testComponent.runTest { + testScope.runTest { val values by collectValues(underTest.deviceEntryParentViewAlpha) shadeExpanded(true) runCurrent() @@ -176,7 +148,7 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Test fun deviceEntryParentViewAlpha_shadeNotExpanded() = - testComponent.runTest { + testScope.runTest { val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) shadeExpanded(false) runCurrent() @@ -208,4 +180,14 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { ownerName = "LockscreenToOccludedTransitionViewModelTest" ) } + + private fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index f4293f035cd1..50f0eb4eca2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -94,6 +94,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { fakeHandler, configurationController, ResourcesSplitShadeStateController(), + mock<KeyguardMediaControllerLogger>(), mock<DumpManager>() ) keyguardMediaController.attachSinglePaneContainer(mediaContainerView) @@ -104,7 +105,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { fun testHiddenWhenHostIsHidden() { whenever(mediaHost.visible).thenReturn(false) - keyguardMediaController.refreshMediaPosition() + keyguardMediaController.refreshMediaPosition(TEST_REASON) assertThat(mediaContainerView.visibility).isEqualTo(GONE) } @@ -118,7 +119,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { private fun testStateVisibility(state: Int, visibility: Int) { whenever(statusBarStateController.state).thenReturn(state) - keyguardMediaController.refreshMediaPosition() + keyguardMediaController.refreshMediaPosition(TEST_REASON) assertThat(mediaContainerView.visibility).isEqualTo(visibility) } @@ -126,7 +127,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { fun testHiddenOnKeyguard_whenMediaOnLockScreenDisabled() { settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 0) - keyguardMediaController.refreshMediaPosition() + keyguardMediaController.refreshMediaPosition(TEST_REASON) assertThat(mediaContainerView.visibility).isEqualTo(GONE) } @@ -135,7 +136,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { fun testAvailableOnKeyguard_whenMediaOnLockScreenEnabled() { settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1) - keyguardMediaController.refreshMediaPosition() + keyguardMediaController.refreshMediaPosition(TEST_REASON) assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) } @@ -234,4 +235,8 @@ class KeyguardMediaControllerTest : SysuiTestCase() { whenever(statusBarStateController.isDozing).thenReturn(true) statusBarStateListener.onDozingChanged(true) } + + private companion object { + private const val TEST_REASON = "test reason" + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 9dfb5a5dcb3d..e082ca81ba4f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -47,13 +47,13 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; @@ -305,7 +305,11 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { MediaOutputBaseDialogImpl(Context context, BroadcastSender broadcastSender, MediaOutputController mediaOutputController) { - super(context, broadcastSender, mediaOutputController); + super( + context, + broadcastSender, + mediaOutputController, /* includePlaybackAndAppMetadata */ + true); mAdapter = mMediaOutputBaseAdapter; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index 379136b0586f..d5dc502b1e6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -49,13 +49,13 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; @@ -394,8 +394,14 @@ public class MediaOutputDialogTest extends SysuiTestCase { @NonNull private MediaOutputDialog makeTestDialog(MediaOutputController controller) { - return new MediaOutputDialog(mContext, false, mBroadcastSender, - controller, mDialogLaunchAnimator, mUiEventLogger); + return new MediaOutputDialog( + mContext, + false, + mBroadcastSender, + controller, + mDialogLaunchAnimator, + mUiEventLogger, + true); } private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index cf43b2e12283..b7618d290f53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -45,7 +45,6 @@ import android.provider.Settings import androidx.test.ext.truth.content.IntentSubject.assertThat import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID @@ -56,6 +55,7 @@ import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity +import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -162,6 +162,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { noteTaskBubblesController = FakeNoteTaskBubbleController(context, testDispatcher, Optional.ofNullable(bubbles)), applicationScope = testScope, + bgCoroutineContext = testScope.backgroundScope.coroutineContext ) // region onBubbleExpandChanged diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt index a9f8ea0194c1..81d02b8043b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt @@ -85,7 +85,7 @@ class CustomTileStatePersisterTest : SysuiTestCase() { `when`(sharedPreferences.edit()).thenReturn(editor) tile = Tile() - customTileStatePersister = CustomTileStatePersister(mockContext) + customTileStatePersister = CustomTileStatePersisterImpl(mockContext) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt new file mode 100644 index 000000000000..937744db500e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.airplate.domain.interactor + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor +import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class AirplaneModeTileDataInteractorTest : SysuiTestCase() { + + private val airplaneModeRepository = FakeAirplaneModeRepository() + + private val underTest: AirplaneModeTileDataInteractor = + AirplaneModeTileDataInteractor(airplaneModeRepository) + + @Test + fun alwaysAvailable() = runTest { + val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) + + assertThat(availability).hasSize(1) + assertThat(availability.last()).isTrue() + } + + @Test + fun dataMatchesTheRepository() = runTest { + val dataList: List<AirplaneModeTileModel> by + collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))) + runCurrent() + + airplaneModeRepository.setIsAirplaneMode(true) + runCurrent() + + airplaneModeRepository.setIsAirplaneMode(false) + runCurrent() + + assertThat(dataList).hasSize(3) + assertThat(dataList.map { it.isEnabled }).isEqualTo(listOf(false, true, false)) + } + + private companion object { + + val TEST_USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..81bde8188f5e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.airplate.domain.interactor + +import android.provider.Settings +import android.telephony.TelephonyManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject.Companion.assertThat +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick +import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository +import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AirplaneModeTileUserActionInteractorTest : SysuiTestCase() { + + private val mobileConnectionsRepository = FakeMobileConnectionsRepository() + private val connectivityRepository = FakeConnectivityRepository() + private val airplaneModeRepository = FakeAirplaneModeRepository() + private val inputHandler = FakeQSTileIntentUserInputHandler() + + private val underTest = + AirplaneModeTileUserActionInteractor( + AirplaneModeInteractor( + airplaneModeRepository, + connectivityRepository, + mobileConnectionsRepository, + ), + inputHandler + ) + + @Test + fun handleClickInEcmMode() = runTest { + val isInAirplaneMode = false + airplaneModeRepository.setIsAirplaneMode(isInAirplaneMode) + mobileConnectionsRepository.setIsInEcmState(true) + + underTest.handleInput(click(AirplaneModeTileModel(isInAirplaneMode))) + + assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action) + .isEqualTo(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS) + } + assertThat(airplaneModeRepository.isAirplaneMode.value).isFalse() + } + + @Test + fun handleClickNotInEcmMode() = runTest { + val isInAirplaneMode = false + airplaneModeRepository.setIsAirplaneMode(isInAirplaneMode) + mobileConnectionsRepository.setIsInEcmState(isInAirplaneMode) + + underTest.handleInput(click(AirplaneModeTileModel(false))) + + assertThat(inputHandler).handledNoInputs() + assertThat(airplaneModeRepository.isAirplaneMode.value).isTrue() + } + + @Test + fun handleLongClick() = runTest { + underTest.handleInput(longClick(AirplaneModeTileModel(false))) + + assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_AIRPLANE_MODE_SETTINGS) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt new file mode 100644 index 000000000000..cf076c557765 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.custom.data.repository + +import android.content.ComponentName +import android.graphics.drawable.Icon +import android.os.UserHandle +import android.service.quicksettings.Tile +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.external.FakeCustomTileStatePersister +import com.android.systemui.qs.external.TileServiceKey +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat +import com.android.systemui.qs.tiles.impl.custom.commons.copy +import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class CustomTileRepositoryTest : SysuiTestCase() { + + private val testScope = TestScope() + + private val persister = FakeCustomTileStatePersister() + + private val underTest: CustomTileRepository = + CustomTileRepositoryImpl( + TileSpec.create(TEST_COMPONENT), + persister, + testScope.testScheduler, + ) + + @Test + fun persistableTileIsRestoredForUser() = + testScope.runTest { + persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1) + persister.persistState(TEST_TILE_KEY_2, TEST_TILE_2) + + underTest.restoreForTheUserIfNeeded(TEST_USER_1, true) + runCurrent() + + assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1) + assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1) + } + + @Test + fun notPersistableTileIsNotRestored() = + testScope.runTest { + persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1) + val tiles = collectValues(underTest.getTiles(TEST_USER_1)) + + underTest.restoreForTheUserIfNeeded(TEST_USER_1, false) + runCurrent() + + assertThat(tiles()).isEmpty() + } + + @Test + fun emptyPersistedStateIsHandled() = + testScope.runTest { + val tiles = collectValues(underTest.getTiles(TEST_USER_1)) + + underTest.restoreForTheUserIfNeeded(TEST_USER_1, true) + runCurrent() + + assertThat(tiles()).isEmpty() + } + + @Test + fun updatingWithPersistableTilePersists() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1) + } + + @Test + fun updatingWithNotPersistableTileDoesntPersist() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false) + runCurrent() + + assertThat(persister.readState(TEST_TILE_KEY_1)).isNull() + } + + @Test + fun updateWithTileEmits() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1) + assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1) + } + + @Test + fun updatingPeristableWithDefaultsPersists() = + testScope.runTest { + underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true) + runCurrent() + + assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1) + } + + @Test + fun updatingNotPersistableWithDefaultsDoesntPersist() = + testScope.runTest { + underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false) + runCurrent() + + assertThat(persister.readState(TEST_TILE_KEY_1)).isNull() + } + + @Test + fun updatingPeristableWithErrorDefaultsDoesntPersist() = + testScope.runTest { + underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true) + runCurrent() + + assertThat(persister.readState(TEST_TILE_KEY_1)).isNull() + } + + @Test + fun updateWithDefaultsEmits() = + testScope.runTest { + underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true) + runCurrent() + + assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1) + assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1) + } + + @Test + fun getTileForAnotherUserReturnsNull() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + assertThat(underTest.getTile(TEST_USER_2)).isNull() + } + + @Test + fun getTilesForAnotherUserEmpty() = + testScope.runTest { + val tiles = collectValues(underTest.getTiles(TEST_USER_2)) + + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + assertThat(tiles()).isEmpty() + } + + @Test + fun updatingWithTileForTheSameUserAddsData() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true) + runCurrent() + + val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" } + assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile) + assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile) + } + + @Test + fun updatingWithTileForAnotherUserOverridesTile() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true) + runCurrent() + + val tiles = collectValues(underTest.getTiles(TEST_USER_2)) + underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true) + runCurrent() + + assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2) + assertThat(tiles()).hasSize(1) + assertThat(tiles().last()).isEqualTo(TEST_TILE_2) + } + + @Test + fun updatingWithDefaultsForTheSameUserAddsData() = + testScope.runTest { + underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true) + runCurrent() + + underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true) + runCurrent() + + val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" } + assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile) + assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile) + } + + @Test + fun updatingWithDefaultsForAnotherUserOverridesTile() = + testScope.runTest { + underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true) + runCurrent() + + val tiles = collectValues(underTest.getTiles(TEST_USER_2)) + underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true) + runCurrent() + + assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2) + assertThat(tiles()).hasSize(1) + assertThat(tiles().last()).isEqualTo(TEST_TILE_2) + } + + private companion object { + + val TEST_COMPONENT = ComponentName("test.pkg", "test.cls") + + val TEST_USER_1 = UserHandle.of(1)!! + val TEST_TILE_1 = + Tile().apply { + label = "test_tile_1" + icon = Icon.createWithContentUri("file://test_1") + } + val TEST_TILE_KEY_1 = TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier) + val TEST_DEFAULTS_1 = CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label) + + val TEST_USER_2 = UserHandle.of(2)!! + val TEST_TILE_2 = + Tile().apply { + label = "test_tile_2" + icon = Icon.createWithContentUri("file://test_2") + } + val TEST_TILE_KEY_2 = TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier) + val TEST_DEFAULTS_2 = CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt new file mode 100644 index 000000000000..eebb145ef384 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.custom.domain.interactor + +import android.content.ComponentName +import android.graphics.drawable.Icon +import android.os.UserHandle +import android.service.quicksettings.Tile +import android.text.format.DateUtils +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.external.FakeCustomTileStatePersister +import com.android.systemui.qs.external.TileServiceKey +import com.android.systemui.qs.external.TileServiceManager +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat +import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults +import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository +import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class CustomTileInteractorTest : SysuiTestCase() { + + @Mock private lateinit var tileServiceManager: TileServiceManager + + private val testScope = TestScope() + + private val defaultsRepository = FakeCustomTileDefaultsRepository() + private val customTileStatePersister = FakeCustomTileStatePersister() + private val customTileRepository = + FakeCustomTileRepository( + TEST_TILE_SPEC, + customTileStatePersister, + testScope.testScheduler, + ) + + private lateinit var underTest: CustomTileInteractor + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + underTest = + CustomTileInteractor( + TEST_USER, + defaultsRepository, + customTileRepository, + tileServiceManager, + testScope.backgroundScope, + testScope.testScheduler, + ) + } + + @Test + fun activeTileIsAvailableAfterRestored() = + testScope.runTest { + whenever(tileServiceManager.isActiveTile).thenReturn(true) + customTileStatePersister.persistState( + TileServiceKey(TEST_COMPONENT, TEST_USER.identifier), + TEST_TILE, + ) + + underTest.init() + + assertThat(underTest.tile).isEqualTo(TEST_TILE) + assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE) + } + + @Test + fun notActiveTileIsAvailableAfterUpdated() = + testScope.runTest { + whenever(tileServiceManager.isActiveTile).thenReturn(false) + customTileStatePersister.persistState( + TileServiceKey(TEST_COMPONENT, TEST_USER.identifier), + TEST_TILE, + ) + val tiles = collectValues(underTest.tiles) + val initJob = launch { underTest.init() } + + underTest.updateTile(TEST_TILE) + runCurrent() + initJob.join() + + assertThat(tiles()).hasSize(1) + assertThat(tiles().last()).isEqualTo(TEST_TILE) + } + + @Test + fun notActiveTileIsAvailableAfterDefaultsUpdated() = + testScope.runTest { + whenever(tileServiceManager.isActiveTile).thenReturn(false) + customTileStatePersister.persistState( + TileServiceKey(TEST_COMPONENT, TEST_USER.identifier), + TEST_TILE, + ) + val tiles = collectValues(underTest.tiles) + val initJob = launch { underTest.init() } + + defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS) + defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT) + runCurrent() + initJob.join() + + assertThat(tiles()).hasSize(1) + assertThat(tiles().last()).isEqualTo(TEST_TILE) + } + + @Test(expected = IllegalStateException::class) + fun getTileBeforeInitThrows() = testScope.runTest { underTest.tile } + + @Test + fun initSuspendsForActiveTileNotRestoredAndNotUpdated() = + testScope.runTest { + whenever(tileServiceManager.isActiveTile).thenReturn(true) + val tiles = collectValues(underTest.tiles) + + val initJob = backgroundScope.launch { underTest.init() } + advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS) + + // Is still suspended + assertThat(initJob.isActive).isTrue() + assertThat(tiles()).isEmpty() + } + + @Test + fun initSuspendedForNotActiveTileWithoutUpdates() = + testScope.runTest { + whenever(tileServiceManager.isActiveTile).thenReturn(false) + customTileStatePersister.persistState( + TileServiceKey(TEST_COMPONENT, TEST_USER.identifier), + TEST_TILE, + ) + val tiles = collectValues(underTest.tiles) + + val initJob = backgroundScope.launch { underTest.init() } + advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS) + + // Is still suspended + assertThat(initJob.isActive).isTrue() + assertThat(tiles()).isEmpty() + } + + private companion object { + + val TEST_COMPONENT = ComponentName("test.pkg", "test.cls") + val TEST_TILE_SPEC = TileSpec.create(TEST_COMPONENT) + val TEST_USER = UserHandle.of(1)!! + val TEST_TILE = + Tile().apply { + label = "test_tile_1" + icon = Icon.createWithContentUri("file://test_1") + } + val TEST_DEFAULTS = CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label) + } +} 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 94dcf7a18514..0ba820f0972a 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 @@ -116,27 +116,27 @@ class FooterViewModelTest : SysuiTestCase() { @Test fun testMessageVisible_whenFilteredNotifications() = testComponent.runTest { - val message by collectLastValue(footerViewModel.message) + val visible by collectLastValue(footerViewModel.message.isVisible) activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true - assertThat(message?.visible).isTrue() + assertThat(visible).isTrue() } @Test fun testMessageVisible_whenNoFilteredNotifications() = testComponent.runTest { - val message by collectLastValue(footerViewModel.message) + val visible by collectLastValue(footerViewModel.message.isVisible) activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false - assertThat(message?.visible).isFalse() + assertThat(visible).isFalse() } @Test fun testClearAllButtonVisible_whenHasClearableNotifs() = testComponent.runTest { - val button by collectLastValue(footerViewModel.clearAllButton) + val visible by collectLastValue(footerViewModel.clearAllButton.isVisible) activeNotificationListRepository.notifStats.value = NotifStats( @@ -148,13 +148,13 @@ class FooterViewModelTest : SysuiTestCase() { ) runCurrent() - assertThat(button?.isVisible?.value).isTrue() + assertThat(visible?.value).isTrue() } @Test fun testClearAllButtonVisible_whenHasNoClearableNotifs() = testComponent.runTest { - val button by collectLastValue(footerViewModel.clearAllButton) + val visible by collectLastValue(footerViewModel.clearAllButton.isVisible) activeNotificationListRepository.notifStats.value = NotifStats( @@ -166,13 +166,13 @@ class FooterViewModelTest : SysuiTestCase() { ) runCurrent() - assertThat(button?.isVisible?.value).isFalse() + assertThat(visible?.value).isFalse() } @Test fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() = testComponent.runTest { - val button by collectLastValue(footerViewModel.clearAllButton) + val visible by collectLastValue(footerViewModel.clearAllButton.isVisible) runCurrent() // WHEN shade is expanded @@ -200,13 +200,13 @@ class FooterViewModelTest : SysuiTestCase() { runCurrent() // THEN button visibility should animate - assertThat(button?.isVisible?.isAnimating).isTrue() + assertThat(visible?.isAnimating).isTrue() } @Test fun testClearAllButtonAnimating_whenShadeNotExpanded() = testComponent.runTest { - val button by collectLastValue(footerViewModel.clearAllButton) + val visible by collectLastValue(footerViewModel.clearAllButton.isVisible) runCurrent() // WHEN shade is collapsed @@ -234,6 +234,6 @@ class FooterViewModelTest : SysuiTestCase() { runCurrent() // THEN button visibility should not animate - assertThat(button?.isVisible?.isAnimating).isFalse() + assertThat(visible?.isAnimating).isFalse() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt new file mode 100644 index 000000000000..0356c2cdbcd0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.interruption + +import android.hardware.display.AmbientDisplayConfiguration +import android.os.Handler +import android.os.PowerManager +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.notification.NotifPipelineFlags +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.EventLog +import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.time.SystemClock + +object VisualInterruptionDecisionProviderTestUtil { + fun createProviderByFlag( + ambientDisplayConfiguration: AmbientDisplayConfiguration, + batteryController: BatteryController, + deviceProvisionedController: DeviceProvisionedController, + eventLog: EventLog, + flags: NotifPipelineFlags, + globalSettings: GlobalSettings, + headsUpManager: HeadsUpManager, + keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider, + keyguardStateController: KeyguardStateController, + @Main mainHandler: Handler, + newLogger: VisualInterruptionDecisionLogger, + oldLogger: NotificationInterruptLogger, + powerManager: PowerManager, + statusBarStateController: StatusBarStateController, + systemClock: SystemClock, + uiEventLogger: UiEventLogger, + userTracker: UserTracker + ): VisualInterruptionDecisionProvider { + return if (VisualInterruptionRefactor.isEnabled) { + VisualInterruptionDecisionProviderImpl( + ambientDisplayConfiguration, + batteryController, + deviceProvisionedController, + eventLog, + globalSettings, + headsUpManager, + keyguardNotificationVisibilityProvider, + keyguardStateController, + newLogger, + mainHandler, + powerManager, + statusBarStateController, + systemClock, + uiEventLogger, + userTracker + ) + } else { + NotificationInterruptStateProviderWrapper( + NotificationInterruptStateProviderImpl( + powerManager, + ambientDisplayConfiguration, + batteryController, + statusBarStateController, + keyguardStateController, + headsUpManager, + oldLogger, + mainHandler, + flags, + keyguardNotificationVisibilityProvider, + uiEventLogger, + userTracker, + deviceProvisionedController, + systemClock, + globalSettings, + eventLog + ) + ) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 3d7cff44322b..ac7c2aa98065 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -15,36 +15,36 @@ * */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.TestMocksModule -import com.android.systemui.collectLastValue -import com.android.systemui.common.shared.model.SharedNotificationContainerPosition -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.common.shared.model.NotificationContainerBounds +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue 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.KeyguardInteractor +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor -import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component +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 @@ -52,45 +52,29 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SharedNotificationContainerViewModelTest : SysuiTestCase() { - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - UserDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<SharedNotificationContainerViewModel> { - - val configurationRepository: FakeConfigurationRepository - val keyguardRepository: FakeKeyguardRepository - val keyguardInteractor: KeyguardInteractor - val keyguardTransitionRepository: FakeKeyguardTransitionRepository - val shadeRepository: FakeShadeRepository - val sharedNotificationContainerInteractor: SharedNotificationContainerInteractor - - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - featureFlags: FakeFeatureFlagsClassicModule, - mocks: TestMocksModule, - ): TestComponent + val kosmos = + testKosmos().apply { + featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } + val testScope = kosmos.testScope + val configurationRepository = kosmos.fakeConfigurationRepository + val keyguardRepository = kosmos.fakeKeyguardRepository + val keyguardInteractor = kosmos.keyguardInteractor + val keyguardRootViewModel = kosmos.keyguardRootViewModel + val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + val shadeRepository = kosmos.shadeRepository + val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor + + val underTest = kosmos.sharedNotificationContainerViewModel + + @Before + fun setUp() { + overrideResource(R.bool.config_use_split_notification_shade, false) } - private val testComponent: TestComponent = - DaggerSharedNotificationContainerViewModelTest_TestComponent.factory() - .create( - test = this, - featureFlags = - FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, - mocks = TestMocksModule(), - ) - @Test fun validateMarginStartInSplitShade() = - testComponent.runTest { + testScope.runTest { overrideResource(R.bool.config_use_split_notification_shade, true) overrideResource(R.dimen.notification_panel_margin_horizontal, 20) @@ -103,7 +87,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginStart() = - testComponent.runTest { + testScope.runTest { overrideResource(R.bool.config_use_split_notification_shade, false) overrideResource(R.dimen.notification_panel_margin_horizontal, 20) @@ -116,7 +100,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginEnd() = - testComponent.runTest { + testScope.runTest { overrideResource(R.dimen.notification_panel_margin_horizontal, 50) val dimens by collectLastValue(underTest.configurationBasedDimensions) @@ -128,7 +112,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginBottom() = - testComponent.runTest { + testScope.runTest { overrideResource(R.dimen.notification_panel_margin_bottom, 50) val dimens by collectLastValue(underTest.configurationBasedDimensions) @@ -140,7 +124,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginTopWithLargeScreenHeader() = - testComponent.runTest { + testScope.runTest { overrideResource(R.bool.config_use_large_screen_shade_header, true) overrideResource(R.dimen.large_screen_shade_header_height, 50) overrideResource(R.dimen.notification_panel_margin_top, 0) @@ -154,7 +138,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginTop() = - testComponent.runTest { + testScope.runTest { overrideResource(R.bool.config_use_large_screen_shade_header, false) overrideResource(R.dimen.large_screen_shade_header_height, 50) overrideResource(R.dimen.notification_panel_margin_top, 0) @@ -168,7 +152,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun isOnLockscreen() = - testComponent.runTest { + testScope.runTest { val isOnLockscreen by collectLastValue(underTest.isOnLockscreen) keyguardTransitionRepository.sendTransitionSteps( @@ -206,7 +190,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun isOnLockscreenWithoutShade() = - testComponent.runTest { + testScope.runTest { val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade) // First on AOD @@ -242,8 +226,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun positionOnLockscreenNotInSplitShade() = - testComponent.runTest { - val position by collectLastValue(underTest.position) + testScope.runTest { + val position by collectLastValue(underTest.bounds) // When not in split shade overrideResource(R.bool.config_use_split_notification_shade, false) @@ -253,18 +237,17 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // Start on lockscreen showLockscreen() - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top = 1f, bottom = 2f) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = 1f, bottom = 2f) ) - assertThat(position) - .isEqualTo(SharedNotificationContainerPosition(top = 1f, bottom = 2f)) + assertThat(position).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f)) } @Test fun positionOnLockscreenInSplitShade() = - testComponent.runTest { - val position by collectLastValue(underTest.position) + testScope.runTest { + val position by collectLastValue(underTest.bounds) // When in split shade overrideResource(R.bool.config_use_split_notification_shade, true) @@ -274,20 +257,19 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // Start on lockscreen showLockscreen() - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top = 1f, bottom = 2f) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = 1f, bottom = 2f) ) runCurrent() // Top should be overridden to 0f - assertThat(position) - .isEqualTo(SharedNotificationContainerPosition(top = 0f, bottom = 2f)) + assertThat(position).isEqualTo(NotificationContainerBounds(top = 0f, bottom = 2f)) } @Test - fun positionOnShade() = - testComponent.runTest { - val position by collectLastValue(underTest.position) + fun boundsOnShade() = + testScope.runTest { + val bounds by collectLastValue(underTest.bounds) // Start on lockscreen with shade expanded showLockscreenWithShadeExpanded() @@ -295,16 +277,14 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // When not in split shade sharedNotificationContainerInteractor.setTopPosition(10f) - assertThat(position) - .isEqualTo( - SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = true) - ) + assertThat(bounds) + .isEqualTo(NotificationContainerBounds(top = 10f, bottom = 0f, isAnimated = true)) } @Test - fun positionOnQS() = - testComponent.runTest { - val position by collectLastValue(underTest.position) + fun boundsOnQS() = + testScope.runTest { + val bounds by collectLastValue(underTest.bounds) // Start on lockscreen with shade expanded showLockscreenWithQSExpanded() @@ -312,15 +292,13 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // When not in split shade sharedNotificationContainerInteractor.setTopPosition(10f) - assertThat(position) - .isEqualTo( - SharedNotificationContainerPosition(top = 10f, bottom = 0f, animate = false) - ) + assertThat(bounds) + .isEqualTo(NotificationContainerBounds(top = 10f, bottom = 0f, isAnimated = false)) } @Test fun maxNotificationsOnLockscreen() = - testComponent.runTest { + testScope.runTest { var notificationCount = 10 val maxNotifications by collectLastValue(underTest.getMaxNotifications { notificationCount }) @@ -329,8 +307,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top = 1f, bottom = 2f) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = 1f, bottom = 2f) ) assertThat(maxNotifications).isEqualTo(10) @@ -343,7 +321,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() = - testComponent.runTest { + testScope.runTest { var notificationCount = 10 val maxNotifications by collectLastValue(underTest.getMaxNotifications { notificationCount }) @@ -352,8 +330,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top = 1f, bottom = 2f) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = 1f, bottom = 2f) ) assertThat(maxNotifications).isEqualTo(10) @@ -379,7 +357,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun maxNotificationsOnShade() = - testComponent.runTest { + testScope.runTest { val maxNotifications by collectLastValue(underTest.getMaxNotifications { 10 }) // Show lockscreen with shade expanded @@ -387,15 +365,26 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setSharedNotificationContainerPosition( - SharedNotificationContainerPosition(top = 1f, bottom = 2f) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = 1f, bottom = 2f) ) // -1 means No Limit assertThat(maxNotifications).isEqualTo(-1) } - private suspend fun TestComponent.showLockscreen() { + @Test + fun updateBounds_fromKeyguardRoot() = + testScope.runTest { + val bounds by collectLastValue(underTest.bounds) + + val top = 123f + val bottom = 456f + keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom) + assertThat(bounds).isEqualTo(NotificationContainerBounds(top, bottom)) + } + + private suspend fun showLockscreen() { shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(0f) keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) @@ -406,7 +395,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { ) } - private suspend fun TestComponent.showLockscreenWithShadeExpanded() { + private suspend fun showLockscreenWithShadeExpanded() { shadeRepository.setLockscreenShadeExpansion(1f) shadeRepository.setQsExpansion(0f) keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) @@ -417,7 +406,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { ) } - private suspend fun TestComponent.showLockscreenWithQSExpanded() { + private suspend fun showLockscreenWithQSExpanded() { shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(1f) keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) 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 4422764c4bac..e3396364a85a 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 @@ -155,9 +155,9 @@ import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionLogger; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -348,8 +348,6 @@ 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, @@ -358,24 +356,25 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mFakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON); mVisualInterruptionDecisionProvider = - new NotificationInterruptStateProviderWrapper( - new TestableNotificationInterruptStateProviderImpl( - mPowerManager, - mAmbientDisplayConfiguration, - mStatusBarStateController, - mKeyguardStateController, - mBatteryController, - mHeadsUpManager, - mock(NotificationInterruptLogger.class), - new Handler(TestableLooper.get(this).getLooper()), - mock(NotifPipelineFlags.class), - mock(KeyguardNotificationVisibilityProvider.class), - mock(UiEventLogger.class), - mUserTracker, - mDeviceProvisionedController, - mFakeSystemClock, - mFakeGlobalSettings, - mFakeEventLog)); + VisualInterruptionDecisionProviderTestUtil.INSTANCE.createProviderByFlag( + mAmbientDisplayConfiguration, + mBatteryController, + mDeviceProvisionedController, + mFakeEventLog, + mock(NotifPipelineFlags.class), + mFakeGlobalSettings, + mHeadsUpManager, + mock(KeyguardNotificationVisibilityProvider.class), + mKeyguardStateController, + new Handler(TestableLooper.get(this).getLooper()), + mock(VisualInterruptionDecisionLogger.class), + mock(NotificationInterruptLogger.class), + mPowerManager, + mStatusBarStateController, + mFakeSystemClock, + mock(UiEventLogger.class), + mUserTracker); + mVisualInterruptionDecisionProvider.start(); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index c454b45a7312..112368895888 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -61,10 +61,12 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.monet.Style; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; import com.google.common.util.concurrent.MoreExecutors; @@ -88,7 +90,10 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { private static final int USER_SYSTEM = UserHandle.USER_SYSTEM; private static final int USER_SECONDARY = 10; - + @Mock + private JavaAdapter mJavaAdapter; + @Mock + private KeyguardTransitionInteractor mKeyguardTransitionInteractor; private ThemeOverlayController mThemeOverlayController; @Mock private Executor mBgExecutor; @@ -150,11 +155,12 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { .thenReturn(Color.YELLOW); when(mResources.getColor(eq(android.R.color.system_neutral2_500), any())) .thenReturn(Color.BLACK); + mThemeOverlayController = new ThemeOverlayController(mContext, mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle, - mUiModeManager) { + mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) { @VisibleForTesting protected boolean isNightMode() { return false; @@ -736,7 +742,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle, - mUiModeManager) { + mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) { @VisibleForTesting protected boolean isNightMode() { return false; @@ -776,7 +782,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle, - mUiModeManager) { + mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) { @VisibleForTesting protected boolean isNightMode() { return false; diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt index 9fe2f5694dde..14fb054a1d9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt @@ -15,9 +15,10 @@ */ package com.android.systemui.unfold.progress +import android.os.Handler +import android.os.HandlerThread import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED @@ -26,6 +27,8 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING import com.android.systemui.unfold.util.TestFoldStateProvider +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -37,16 +40,28 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { private val foldStateProvider: TestFoldStateProvider = TestFoldStateProvider() private val listener = TestUnfoldProgressListener() private lateinit var progressProvider: UnfoldTransitionProgressProvider + private val schedulerFactory = + mock<UnfoldFrameCallbackScheduler.Factory>().apply { + whenever(create()).then { UnfoldFrameCallbackScheduler() } + } + private val mockBgHandler = mock<Handler>() + private val fakeHandler = Handler(HandlerThread("UnfoldBg").apply { start() }.looper) @Before fun setUp() { - progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(context, foldStateProvider) + progressProvider = + PhysicsBasedUnfoldTransitionProgressProvider( + context, + schedulerFactory, + foldStateProvider = foldStateProvider, + progressHandler = fakeHandler + ) progressProvider.addCallback(listener) } @Test fun testUnfold_emitsIncreasingTransitionEvents() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendUnfoldedScreenAvailable() }, @@ -63,7 +78,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Test fun testUnfold_emitsFinishingEvent() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendUnfoldedScreenAvailable() }, @@ -77,7 +92,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Test fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendHingeAngleUpdate(90f) }, @@ -94,7 +109,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Test fun testFold_emitsDecreasingTransitionEvents() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) }, { foldStateProvider.sendHingeAngleUpdate(170f) }, { foldStateProvider.sendHingeAngleUpdate(90f) }, @@ -110,7 +125,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Test fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendUnfoldedScreenAvailable() }, { foldStateProvider.sendHingeAngleUpdate(10f) }, @@ -126,7 +141,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Test fun testFoldImmediatelyAfterUnfold_runsFoldAnimation() { - runOnMainThreadWithInterval( + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendUnfoldedScreenAvailable() }, { foldStateProvider.sendHingeAngleUpdate(10f) }, @@ -144,9 +159,12 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { with(listener.ensureTransitionFinished()) { assertHasFoldAnimationAtTheEnd() } } - private fun runOnMainThreadWithInterval(vararg blocks: () -> Unit, intervalMillis: Long = 60) { + private fun runOnProgressThreadWithInterval( + vararg blocks: () -> Unit, + intervalMillis: Long = 60, + ) { blocks.forEach { - InstrumentationRegistry.getInstrumentation().runOnMainSync { it() } + fakeHandler.post(it) Thread.sleep(intervalMillis) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index aa492871a079..552b60cbeb21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -37,7 +37,6 @@ import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenLis import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import junit.framework.Assert.fail import java.util.concurrent.Executor @@ -105,16 +104,15 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { foldStateProvider = DeviceFoldStateProvider( - config, - testHingeAngleProvider, - screenOnStatusProvider, - foldProvider, - activityTypeProvider, - unfoldKeyguardVisibilityProvider, - rotationChangeProvider, - context, - context.mainExecutor, - handler + config, + context, + screenOnStatusProvider, + activityTypeProvider, + unfoldKeyguardVisibilityProvider, + foldProvider, + testHingeAngleProvider, + rotationChangeProvider, + handler ) foldStateProvider.addCallback( @@ -151,6 +149,12 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { null } + whenever(handler.post(any<Runnable>())).then { invocationOnMock -> + val runnable = invocationOnMock.getArgument<Runnable>(0) + runnable.run() + null + } + // By default, we're on launcher. setupForegroundActivityType(isHomeActivity = true) setIsLargeScreen(true) @@ -171,7 +175,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { } @Test - fun testOnUnfold_hingeAngleDecreasesBeforeInnerScreenAvailable_emitsOnlyStartAndInnerScreenAvailableEvents() { + fun onUnfold_angleDecrBeforeInnerScrAvailable_emitsOnlyStartAndInnerScrAvailableEvents() { setFoldState(folded = true) foldUpdates.clear() @@ -187,7 +191,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { } @Test - fun testOnUnfold_hingeAngleDecreasesAfterInnerScreenAvailable_emitsStartInnerScreenAvailableAndStartClosingEvents() { + fun onUnfold_angleDecrAfterInnerScrAvailable_emitsStartInnerScrAvailableAndStartClosingEvnts() { setFoldState(folded = true) foldUpdates.clear() @@ -690,7 +694,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { callbacks.forEach { it.onFoldUpdated(isFolded) } } - fun getNumberOfCallbacks(): Int{ + fun getNumberOfCallbacks(): Int { return callbacks.size } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt index 6d2f00d50d78..080689a5ca8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/LoopedAnimatable2DrawableWrapperTest.kt @@ -59,13 +59,36 @@ class LoopedAnimatable2DrawableWrapperTest : SysuiTestCase() { } @Test + fun multipleStartAddsTheCallbackOnce() { + underTest.start() + underTest.start() + underTest.start() + underTest.start() + + verify(drawable).registerAnimationCallback(any()) + } + + @Test fun stopRemovesTheCallback() { + underTest.start() + underTest.stop() verify(drawable).unregisterAnimationCallback(any()) } @Test + fun callbackSurvivesClearAnimationCallbacks() { + underTest.start() + + underTest.clearAnimationCallbacks() + + verify(drawable).clearAnimationCallbacks() + // start + re-add after #clearAnimationCallbacks + verify(drawable, times(2)).registerAnimationCallback(capture(callbackCaptor)) + } + + @Test fun animationLooped() { underTest.start() verify(drawable).registerAnimationCallback(capture(callbackCaptor)) 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 ca167ad3cd14..52c25f7b2b71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -145,8 +145,9 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; 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.interruption.VisualInterruptionDecisionLogger; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil; 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; @@ -367,9 +368,6 @@ 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(); } @@ -524,7 +522,8 @@ public class BubblesTest extends SysuiTestCase { (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0; }); - mPositioner = new TestableBubblePositioner(mContext, mWindowManager); + mPositioner = new TestableBubblePositioner(mContext, + mContext.getSystemService(WindowManager.class)); mPositioner.setMaxBubbles(5); mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, mEducationController, syncExecutor); @@ -535,25 +534,26 @@ public class BubblesTest extends SysuiTestCase { final FakeGlobalSettings fakeGlobalSettings = new FakeGlobalSettings(); fakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON); - TestableNotificationInterruptStateProviderImpl interruptionStateProvider = - new TestableNotificationInterruptStateProviderImpl( - mock(PowerManager.class), + final VisualInterruptionDecisionProvider interruptionDecisionProvider = + VisualInterruptionDecisionProviderTestUtil.INSTANCE.createProviderByFlag( mock(AmbientDisplayConfiguration.class), - mock(StatusBarStateController.class), - mock(KeyguardStateController.class), mock(BatteryController.class), - mock(HeadsUpManager.class), - mock(NotificationInterruptLogger.class), - mock(Handler.class), + mock(DeviceProvisionedController.class), + new FakeEventLog(), mock(NotifPipelineFlags.class), + fakeGlobalSettings, + mock(HeadsUpManager.class), mock(KeyguardNotificationVisibilityProvider.class), - mock(UiEventLogger.class), - mock(UserTracker.class), - mock(DeviceProvisionedController.class), + mock(KeyguardStateController.class), + mock(Handler.class), + mock(VisualInterruptionDecisionLogger.class), + mock(NotificationInterruptLogger.class), + mock(PowerManager.class), + mock(StatusBarStateController.class), mock(SystemClock.class), - fakeGlobalSettings, - new FakeEventLog() - ); + mock(UiEventLogger.class), + mock(UserTracker.class)); + interruptionDecisionProvider.start(); mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class), mock(ShellCommandHandler.class), @@ -602,7 +602,7 @@ public class BubblesTest extends SysuiTestCase { mock(INotificationManager.class), mIDreamManager, mVisibilityProvider, - new NotificationInterruptStateProviderWrapper(interruptionStateProvider), + interruptionDecisionProvider, mZenModeController, mLockscreenUserManager, mCommonNotifCollection, diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt index 8e55695bc032..46259a6964cd 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt @@ -14,7 +14,9 @@ * limitations under the License. */ -@GraphicsMode(GraphicsMode.Mode.NATIVE) -package com.android.settingslib.spa.screenshot.widget.button; +package com.android.systemui -import org.robolectric.annotation.GraphicsMode; +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase + +fun SysuiTestCase.testKosmos(): Kosmos = Kosmos().apply { testCase = this@testKosmos } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt new file mode 100644 index 000000000000..8702e00b1e97 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.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.biometrics.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.fingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt new file mode 100644 index 000000000000..b04161a7809f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.keyguard.data.repository.biometricSettingsRepository +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.deviceEntryUdfpsInteractor by Fixture { + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = deviceEntryFingerprintAuthRepository, + biometricSettingsRepository = biometricSettingsRepository, + ) +} diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/doze/util/BurnInHelperWrapperKosmos.kt index fd6a5dda5ca8..0f7945fb9846 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/doze/util/BurnInHelperWrapperKosmos.kt @@ -14,7 +14,9 @@ * limitations under the License. */ -@GraphicsMode(GraphicsMode.Mode.NATIVE) -package com.android.settingslib.spa.screenshot.widget.preference; +package com.android.systemui.doze.util -import org.robolectric.annotation.GraphicsMode; +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.burnInHelperWrapper by Fixture { BurnInHelperWrapper() } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt index 0089c2e26e34..45d39b0d4bc4 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt @@ -14,7 +14,9 @@ * limitations under the License. */ -@GraphicsMode(GraphicsMode.Mode.NATIVE) -package com.android.settingslib.spa.screenshot.widget.illustration; +package com.android.systemui.keyguard.data.repository -import org.robolectric.annotation.GraphicsMode; +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.biometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt new file mode 100644 index 000000000000..6437ef3e50f4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.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 +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.deviceEntryFingerprintAuthRepository by Fixture { + FakeDeviceEntryFingerprintAuthRepository() +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt new file mode 100644 index 000000000000..b0d941dc6c24 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.domain.interactor + +import android.content.applicationContext +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.doze.util.burnInHelperWrapper +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.burnInInteractor by Fixture { + BurnInInteractor( + context = applicationContext, + burnInHelperWrapper = burnInHelperWrapper, + scope = applicationCoroutineScope, + configurationRepository = configurationRepository, + keyguardInteractor = keyguardInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt new file mode 100644 index 000000000000..a31ab3ee1fc4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.keyguard.ui.viewmodel + +import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.aodToLockscreenTransitionViewModel by Fixture { + AodToLockscreenTransitionViewModel( + interactor = keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt new file mode 100644 index 000000000000..5db95cf3ebc5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.keyguard.ui.viewmodel + +import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.goneToAodTransitionViewModel by Fixture { + GoneToAodTransitionViewModel( + interactor = keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt new file mode 100644 index 000000000000..663b8450e690 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.applicationContext +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.keyguard.domain.interactor.burnInInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor +import com.android.systemui.statusbar.phone.dozeParameters +import com.android.systemui.statusbar.phone.screenOffAnimationController +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.keyguardRootViewModel by Fixture { + KeyguardRootViewModel( + context = applicationContext, + deviceEntryInteractor = deviceEntryInteractor, + dozeParameters = dozeParameters, + keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + notificationsKeyguardInteractor = notificationsKeyguardInteractor, + burnInInteractor = burnInInteractor, + goneToAodTransitionViewModel = goneToAodTransitionViewModel, + aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, + screenOffAnimationController = screenOffAnimationController, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlowsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlowsKosmos.kt new file mode 100644 index 000000000000..f533bcad755e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlowsKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.shade.domain.interactor.shadeInteractor + +val Kosmos.shadeDependentFlows by Fixture { + ShadeDependentFlows( + transitionInteractor = keyguardTransitionInteractor, + shadeInteractor = shadeInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.kt new file mode 100644 index 000000000000..29702eb2cdc9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.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.qs.external + +import android.service.quicksettings.Tile + +class FakeCustomTileStatePersister : CustomTileStatePersister { + + private val tiles: MutableMap<TileServiceKey, Tile> = mutableMapOf() + + override fun readState(key: TileServiceKey): Tile? = tiles[key] + + override fun persistState(key: TileServiceKey, tile: Tile) { + tiles[key] = tile + } + + override fun removeState(key: TileServiceKey) { + tiles.remove(key) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt new file mode 100644 index 000000000000..d2351dc8ae18 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.custom + +import android.service.quicksettings.Tile +import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat +import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.tiles +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.google.common.truth.Subject.Factory +import com.google.common.truth.Truth + +/** + * [Tile]-specific extension for [Truth]. Use [assertThat] or [tiles] to get an instance of this + * subject. + */ +class TileSubject private constructor(failureMetadata: FailureMetadata, subject: Tile?) : + Subject(failureMetadata, subject) { + + private val actual: Tile? = subject + + /** Asserts if the [Tile] fields are the same. */ + fun isEqualTo(other: Tile?) { + if (actual == null) { + check("other").that(other).isNull() + return + } else { + check("other").that(other).isNotNull() + other ?: return + } + + check("icon").that(actual.icon).isEqualTo(other.icon) + check("label").that(actual.label).isEqualTo(other.label) + check("subtitle").that(actual.subtitle).isEqualTo(other.subtitle) + check("contentDescription") + .that(actual.contentDescription) + .isEqualTo(other.contentDescription) + check("stateDescription").that(actual.stateDescription).isEqualTo(other.stateDescription) + check("activityLaunchForClick") + .that(actual.activityLaunchForClick) + .isEqualTo(other.activityLaunchForClick) + check("state").that(actual.state).isEqualTo(other.state) + } + + companion object { + + /** Returns a factory to be used with [Truth.assertAbout]. */ + fun tiles(): Factory<TileSubject, Tile?> { + return Factory { failureMetadata: FailureMetadata, subject: Tile? -> + TileSubject(failureMetadata, subject) + } + } + + /** Shortcut for `Truth.assertAbout(tiles()).that(tile)`. */ + fun assertThat(tile: Tile?): TileSubject = Truth.assertAbout(tiles()).that(tile) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt index 13910fd5c564..ccba07273f1e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt @@ -19,15 +19,20 @@ package com.android.systemui.qs.tiles.impl.custom.data.repository import android.content.ComponentName import android.os.UserHandle import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull class FakeCustomTileDefaultsRepository : CustomTileDefaultsRepository { private val defaults: MutableMap<DefaultsKey, CustomTileDefaults> = mutableMapOf() - private val defaultsFlow = MutableSharedFlow<DefaultsRequest>() + private val defaultsFlow = + MutableSharedFlow<DefaultsRequest>( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) private val mutableDefaultsRequests: MutableList<DefaultsRequest> = mutableListOf() val defaultsRequests: List<DefaultsRequest> = mutableDefaultsRequests @@ -41,7 +46,7 @@ class FakeCustomTileDefaultsRepository : CustomTileDefaultsRepository { old == new } } - .map { defaults[DefaultsKey(it.user, it.componentName)]!! } + .mapNotNull { defaults[DefaultsKey(it.user, it.componentName)] } override fun requestNewDefaults( user: UserHandle, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt new file mode 100644 index 000000000000..ccf03911495f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.custom.data.repository + +import android.os.UserHandle +import android.service.quicksettings.Tile +import com.android.systemui.qs.external.FakeCustomTileStatePersister +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.flow.Flow + +class FakeCustomTileRepository( + tileSpec: TileSpec.CustomTileSpec, + customTileStatePersister: FakeCustomTileStatePersister, + testBackgroundContext: CoroutineContext, +) : CustomTileRepository { + + private val realDelegate: CustomTileRepository = + CustomTileRepositoryImpl( + tileSpec, + customTileStatePersister, + testBackgroundContext, + ) + + override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) = + realDelegate.restoreForTheUserIfNeeded(user, isPersistable) + + override fun getTiles(user: UserHandle): Flow<Tile> = realDelegate.getTiles(user) + + override fun getTile(user: UserHandle): Tile? = realDelegate.getTile(user) + + override suspend fun updateWithTile( + user: UserHandle, + newTile: Tile, + isPersistable: Boolean, + ) = realDelegate.updateWithTile(user, newTile, isPersistable) + + override suspend fun updateWithDefaults( + user: UserHandle, + defaults: CustomTileDefaults, + isPersistable: Boolean, + ) = realDelegate.updateWithDefaults(user, defaults, isPersistable) +} diff --git a/media/java/android/media/LoudnessCodecFormat.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/location/LocationTileKosmos.kt index 75c906060d43..f4c7ca7a21f7 100644 --- a/media/java/android/media/LoudnessCodecFormat.aidl +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/location/LocationTileKosmos.kt @@ -14,17 +14,11 @@ * limitations under the License. */ -package android.media; +package com.android.systemui.qs.tiles.impl.location +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.statusbar.policy.PolicyModule -/** - * Loudness format which specifies the input attributes used for measuring - * the parameters required to perform loudness alignment as specified by the - * CTA2075 standard. - * - * {@hide} - */ -parcelable LoudnessCodecFormat { - String metadataType; - boolean isDownmixing; -}
\ No newline at end of file +val Kosmos.qsLocationTileConfig by + Kosmos.Fixture { PolicyModule.provideLocationTileConfig(qsEventLogger) } 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 index a3ceef021c59..c2cdbed21abe 100644 --- 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 @@ -18,4 +18,4 @@ package com.android.systemui.scene.shared.flag import com.android.systemui.kosmos.Kosmos -val Kosmos.sceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() } +var Kosmos.sceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepositoryKosmos.kt new file mode 100644 index 000000000000..407390296a6c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepositoryKosmos.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.stack.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.notificationStackAppearanceRepository by Fixture { + NotificationStackAppearanceRepository() +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt new file mode 100644 index 000000000000..546a1e019c6b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.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.statusbar.notification.stack.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.notification.stack.data.repository.notificationStackAppearanceRepository + +val Kosmos.notificationStackAppearanceInteractor by Fixture { + NotificationStackAppearanceInteractor( + repository = notificationStackAppearanceRepository, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt new file mode 100644 index 000000000000..61a38b864c40 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.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.statusbar.notification.stack.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository +import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor + +val Kosmos.notificationsKeyguardInteractor by Fixture { + NotificationsKeyguardInteractor( + repository = notificationsKeyguardViewStateRepository, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt new file mode 100644 index 000000000000..f2f3a5a1ad72 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor + +val Kosmos.notificationStackAppearanceViewModel by Fixture { + NotificationStackAppearanceViewModel( + stackAppearanceInteractor = notificationStackAppearanceInteractor, + shadeInteractor = shadeInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt new file mode 100644 index 000000000000..0dbade76979c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.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.ui.viewmodel + +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.scene.shared.flag.sceneContainerFlags +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor + +val Kosmos.notificationsPlaceholderViewModel by Fixture { + NotificationsPlaceholderViewModel( + interactor = notificationStackAppearanceInteractor, + flags = sceneContainerFlags, + featureFlags = featureFlagsClassic, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt new file mode 100644 index 000000000000..c17083c5fb1c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.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.stack.ui.viewmodel + +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.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor + +val Kosmos.sharedNotificationContainerViewModel by Fixture { + SharedNotificationContainerViewModel( + interactor = sharedNotificationContainerInteractor, + applicationScope = applicationCoroutineScope, + keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + shadeInteractor = shadeInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java index 838a2739f0b0..3c6327514f24 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java @@ -19,8 +19,14 @@ import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback; +import java.util.ArrayList; +import java.util.List; + public class FakeLocationController extends BaseLeakChecker<LocationChangeCallback> implements LocationController { + + private final List<LocationChangeCallback> mCallbacks = new ArrayList<>(); + public FakeLocationController(LeakCheck test) { super(test, "location"); } @@ -37,6 +43,19 @@ public class FakeLocationController extends BaseLeakChecker<LocationChangeCallba @Override public boolean setLocationEnabled(boolean enabled) { + mCallbacks.forEach(callback -> callback.onLocationSettingsChanged(enabled)); return false; } + + @Override + public void addCallback(LocationChangeCallback callback) { + super.addCallback(callback); + mCallbacks.add(callback); + } + + @Override + public void removeCallback(LocationChangeCallback callback) { + super.removeCallback(callback); + mCallbacks.remove(callback); + } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt index a639df539cb9..2bc2db3ba629 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt @@ -16,9 +16,12 @@ package com.android.systemui.unfold +import android.os.Handler import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.dagger.UseReceivingFilter import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver +import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener import dagger.Module import dagger.Provides @@ -33,16 +36,25 @@ class UnfoldRemoteModule { @Singleton fun provideTransitionProvider( config: UnfoldTransitionConfig, - traceListener: ATraceLoggerTransitionProgressListener, + traceListener: ATraceLoggerTransitionProgressListener.Factory, remoteReceiverProvider: Provider<RemoteUnfoldTransitionReceiver>, ): Optional<RemoteUnfoldTransitionReceiver> { if (!config.isEnabled) { return Optional.empty() } val remoteReceiver = remoteReceiverProvider.get() - remoteReceiver.addCallback(traceListener) + remoteReceiver.addCallback(traceListener.create("remoteReceiver")) return Optional.of(remoteReceiver) } @Provides @UseReceivingFilter fun useReceivingFilter(): Boolean = true + + @Provides + @UnfoldMain + fun provideMainRotationChangeProvider( + rotationChangeProviderFactory: RotationChangeProvider.Factory, + @UnfoldMain mainHandler: Handler, + ): RotationChangeProvider { + return rotationChangeProviderFactory.create(mainHandler) + } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt index c3a6cf035d09..31b7ccca49ac 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt @@ -22,12 +22,12 @@ import android.hardware.SensorManager import android.hardware.display.DisplayManager import android.os.Handler import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver import com.android.systemui.unfold.updates.FoldProvider import com.android.systemui.unfold.updates.RotationChangeProvider -import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.util.CurrentActivityTypeProvider import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix @@ -63,13 +63,12 @@ interface UnfoldSharedComponent { @BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor, @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String, @BindsInstance displayManager: DisplayManager, - @BindsInstance contentResolver: ContentResolver = context.contentResolver + @BindsInstance @UnfoldBg bgHandler: Handler, + @BindsInstance contentResolver: ContentResolver = context.contentResolver, ): UnfoldSharedComponent } val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider> - val hingeAngleProvider: HingeAngleProvider - val rotationChangeProvider: RotationChangeProvider } /** @@ -94,7 +93,8 @@ interface RemoteUnfoldSharedComponent { } val remoteTransitionProgress: Optional<RemoteUnfoldTransitionReceiver> - val rotationChangeProvider: RotationChangeProvider + + @UnfoldMain fun getRotationChangeProvider(): RotationChangeProvider } /** diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt index 7473ca6a6486..42d31b38ff76 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt @@ -16,7 +16,10 @@ package com.android.systemui.unfold +import android.os.Handler import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.dagger.UnfoldBg +import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder @@ -24,6 +27,7 @@ import com.android.systemui.unfold.updates.DeviceFoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateRepository import com.android.systemui.unfold.updates.FoldStateRepositoryImpl +import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider @@ -38,16 +42,18 @@ import java.util.Optional import javax.inject.Provider import javax.inject.Singleton -@Module(includes = [UnfoldSharedInternalModule::class]) +@Module( + includes = + [ + UnfoldSharedInternalModule::class, + UnfoldRotationProviderInternalModule::class, + HingeAngleProviderInternalModule::class, + FoldStateProviderModule::class, + ] +) class UnfoldSharedModule { @Provides @Singleton - fun provideFoldStateProvider( - deviceFoldStateProvider: DeviceFoldStateProvider - ): FoldStateProvider = deviceFoldStateProvider - - @Provides - @Singleton fun unfoldKeyguardVisibilityProvider( impl: UnfoldKeyguardVisibilityManagerImpl ): UnfoldKeyguardVisibilityProvider = impl @@ -60,9 +66,7 @@ class UnfoldSharedModule { @Provides @Singleton - fun foldStateRepository( - impl: FoldStateRepositoryImpl - ): FoldStateRepository = impl + fun foldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository = impl } /** @@ -77,17 +81,69 @@ internal class UnfoldSharedInternalModule { fun unfoldTransitionProgressProvider( config: UnfoldTransitionConfig, scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory, + tracingListener: ATraceLoggerTransitionProgressListener.Factory, + physicsBasedUnfoldTransitionProgressProvider: + PhysicsBasedUnfoldTransitionProgressProvider.Factory, + fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>, + foldStateProvider: FoldStateProvider, + @UnfoldMain mainHandler: Handler, + ): Optional<UnfoldTransitionProgressProvider> { + return createOptionalUnfoldTransitionProgressProvider( + config = config, + scaleAwareProviderFactory = scaleAwareProviderFactory, + tracingListener = tracingListener.create("MainThread"), + physicsBasedUnfoldTransitionProgressProvider = + physicsBasedUnfoldTransitionProgressProvider, + fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider, + foldStateProvider = foldStateProvider, + progressHandler = mainHandler, + ) + } + + @Provides + @Singleton + @UnfoldBg + fun unfoldBgTransitionProgressProvider( + config: UnfoldTransitionConfig, + scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory, + tracingListener: ATraceLoggerTransitionProgressListener.Factory, + physicsBasedUnfoldTransitionProgressProvider: + PhysicsBasedUnfoldTransitionProgressProvider.Factory, + fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>, + @UnfoldBg bgFoldStateProvider: FoldStateProvider, + @UnfoldBg bgHandler: Handler, + ): Optional<UnfoldTransitionProgressProvider> { + return createOptionalUnfoldTransitionProgressProvider( + config = config, + scaleAwareProviderFactory = scaleAwareProviderFactory, + tracingListener = tracingListener.create("BgThread"), + physicsBasedUnfoldTransitionProgressProvider = + physicsBasedUnfoldTransitionProgressProvider, + fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider, + foldStateProvider = bgFoldStateProvider, + progressHandler = bgHandler, + ) + } + + private fun createOptionalUnfoldTransitionProgressProvider( + config: UnfoldTransitionConfig, + scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory, tracingListener: ATraceLoggerTransitionProgressListener, physicsBasedUnfoldTransitionProgressProvider: - Provider<PhysicsBasedUnfoldTransitionProgressProvider>, + PhysicsBasedUnfoldTransitionProgressProvider.Factory, fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>, + foldStateProvider: FoldStateProvider, + progressHandler: Handler, ): Optional<UnfoldTransitionProgressProvider> { if (!config.isEnabled) { return Optional.empty() } val baseProgressProvider = if (config.isHingeAngleEnabled) { - physicsBasedUnfoldTransitionProgressProvider.get() + physicsBasedUnfoldTransitionProgressProvider.create( + foldStateProvider, + progressHandler + ) } else { fixedTimingTransitionProgressProvider.get() } @@ -101,26 +157,105 @@ internal class UnfoldSharedInternalModule { } @Provides + @Singleton + fun provideProgressForwarder( + config: UnfoldTransitionConfig, + progressForwarder: Provider<UnfoldTransitionProgressForwarder> + ): Optional<UnfoldTransitionProgressForwarder> { + if (!config.isEnabled) { + return Optional.empty() + } + return Optional.of(progressForwarder.get()) + } +} + +/** + * Provides [FoldStateProvider]. The [UnfoldBg] annotated binding sends progress in the [UnfoldBg] + * handler. + */ +@Module +internal class FoldStateProviderModule { + @Provides + @Singleton + fun provideFoldStateProvider( + factory: DeviceFoldStateProvider.Factory, + @UnfoldMain hingeAngleProvider: HingeAngleProvider, + @UnfoldMain rotationChangeProvider: RotationChangeProvider, + @UnfoldMain mainHandler: Handler, + ): FoldStateProvider = + factory.create( + hingeAngleProvider, + rotationChangeProvider, + progressHandler = mainHandler + ) + + @Provides + @Singleton + @UnfoldBg + fun provideBgFoldStateProvider( + factory: DeviceFoldStateProvider.Factory, + @UnfoldBg hingeAngleProvider: HingeAngleProvider, + @UnfoldBg rotationChangeProvider: RotationChangeProvider, + @UnfoldBg bgHandler: Handler, + ): FoldStateProvider = + factory.create( + hingeAngleProvider, + rotationChangeProvider, + progressHandler = bgHandler + ) +} + +/** Provides bindings for both [UnfoldMain] and [UnfoldBg] [HingeAngleProvider]. */ +@Module +internal class HingeAngleProviderInternalModule { + @Provides + @UnfoldMain fun hingeAngleProvider( config: UnfoldTransitionConfig, - hingeAngleSensorProvider: Provider<HingeSensorAngleProvider> + @UnfoldMain handler: Handler, + hingeAngleSensorProvider: HingeSensorAngleProvider.Factory ): HingeAngleProvider { return if (config.isHingeAngleEnabled) { - hingeAngleSensorProvider.get() + hingeAngleSensorProvider.create(handler) } else { EmptyHingeAngleProvider } } @Provides - @Singleton - fun provideProgressForwarder( - config: UnfoldTransitionConfig, - progressForwarder: Provider<UnfoldTransitionProgressForwarder> - ): Optional<UnfoldTransitionProgressForwarder> { - if (!config.isEnabled) { - return Optional.empty() + @UnfoldBg + fun hingeAngleProviderBg( + config: UnfoldTransitionConfig, + @UnfoldBg handler: Handler, + hingeAngleSensorProvider: HingeSensorAngleProvider.Factory + ): HingeAngleProvider { + return if (config.isHingeAngleEnabled) { + hingeAngleSensorProvider.create(handler) + } else { + EmptyHingeAngleProvider } - return Optional.of(progressForwarder.get()) + } +} + +@Module +internal class UnfoldRotationProviderInternalModule { + @Provides + @Singleton + @UnfoldMain + fun provideRotationChangeProvider( + rotationChangeProviderFactory: RotationChangeProvider.Factory, + @UnfoldMain mainHandler: Handler, + ): RotationChangeProvider { + return rotationChangeProviderFactory.create(mainHandler) + } + + @Provides + @Singleton + @UnfoldBg + fun provideBgRotationChangeProvider( + rotationChangeProviderFactory: RotationChangeProvider.Factory, + @UnfoldBg bgHandler: Handler, + ): RotationChangeProvider { + return rotationChangeProviderFactory.create(bgHandler) } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index 18399194434a..1cbaf3135c4d 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -48,6 +48,7 @@ fun createUnfoldSharedComponent( singleThreadBgExecutor: Executor, tracingTagPrefix: String, displayManager: DisplayManager, + bgHandler: Handler, ): UnfoldSharedComponent = DaggerUnfoldSharedComponent.factory() .create( @@ -62,6 +63,7 @@ fun createUnfoldSharedComponent( singleThreadBgExecutor, tracingTagPrefix, displayManager, + bgHandler, ) /** diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt new file mode 100644 index 000000000000..7cd441950517 --- /dev/null +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.android.systemui.unfold.dagger + +import javax.inject.Qualifier + +/** Annotation for background computations related to unfold lib. */ +@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UnfoldBg diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt index f8f168bd4dc1..907bf46fb981 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt @@ -20,6 +20,7 @@ import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.content.Context +import android.os.Handler import android.os.Trace import android.util.FloatProperty import android.util.Log @@ -38,13 +39,25 @@ import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener import com.android.systemui.unfold.updates.name -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject -/** Maps fold updates to unfold transition progress using DynamicAnimation. */ +/** + * Maps fold updates to unfold transition progress using DynamicAnimation. + * + * Note that all variable accesses must be done in the [Handler] provided in the constructor, that + * might be different than [mainHandler]. When a custom handler is provided, the [SpringAnimation] + * uses a scheduler different than the default one. + */ class PhysicsBasedUnfoldTransitionProgressProvider -@Inject -constructor(context: Context, private val foldStateProvider: FoldStateProvider) : - UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener { +@AssistedInject +constructor( + context: Context, + private val schedulerFactory: UnfoldFrameCallbackScheduler.Factory, + @Assisted private val foldStateProvider: FoldStateProvider, + @Assisted private val progressHandler: Handler, +) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener { private val emphasizedInterpolator = loadInterpolator(context, android.R.interpolator.fast_out_extra_slow_in) @@ -63,6 +76,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) private var transitionProgress: Float = 0.0f set(value) { + assertInProgressThread() if (isTransitionRunning) { listeners.forEach { it.onTransitionProgress(value) } } @@ -72,8 +86,14 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) private val listeners: MutableList<TransitionProgressListener> = mutableListOf() init { - foldStateProvider.addCallback(this) - foldStateProvider.start() + progressHandler.post { + // The scheduler needs to be created in the progress handler in order to get the correct + // choreographer and frame callbacks. This is because the choreographer can be get only + // as a thread local. + springAnimation.scheduler = schedulerFactory.create() + foldStateProvider.addCallback(this) + foldStateProvider.start() + } } override fun destroy() { @@ -81,6 +101,8 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) } override fun onHingeAngleUpdate(angle: Float) { + assertInProgressThread() + if (!isTransitionRunning || isAnimatedCancelRunning) return val progress = saturate(angle / FINAL_HINGE_ANGLE_POSITION) springAnimation.animateToFinalPosition(progress) @@ -90,6 +112,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) if (amount < low) low else if (amount > high) high else amount override fun onFoldUpdate(@FoldUpdate update: Int) { + assertInProgressThread() when (update) { FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_HALF_OPEN -> { @@ -148,6 +171,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) } private fun cancelTransition(endValue: Float, animate: Boolean) { + assertInProgressThread() if (isTransitionRunning && animate) { if (endValue == 1.0f && !isAnimatedCancelRunning) { listeners.forEach { it.onTransitionFinishing() } @@ -165,7 +189,6 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) isAnimatedCancelRunning = false isTransitionRunning = false springAnimation.cancel() - cannedAnimator?.removeAllListeners() cannedAnimator?.cancel() cannedAnimator = null @@ -182,7 +205,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) animation: DynamicAnimation<out DynamicAnimation<*>>, canceled: Boolean, value: Float, - velocity: Float + velocity: Float, ) { if (isAnimatedCancelRunning) { cancelTransition(value, animate = false) @@ -202,6 +225,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) } private fun startTransition(startValue: Float) { + assertInProgressThread() if (!isTransitionRunning) onStartTransition() springAnimation.apply { @@ -221,14 +245,16 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) } override fun addCallback(listener: TransitionProgressListener) { - listeners.add(listener) + progressHandler.post { listeners.add(listener) } } override fun removeCallback(listener: TransitionProgressListener) { - listeners.remove(listener) + progressHandler.post { listeners.remove(listener) } } private fun startCannedCancelAnimation() { + assertInProgressThread() + cannedAnimator?.cancel() cannedAnimator = null @@ -264,7 +290,7 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) override fun setValue( provider: PhysicsBasedUnfoldTransitionProgressProvider, - value: Float + value: Float, ) { provider.transitionProgress = value } @@ -272,6 +298,25 @@ constructor(context: Context, private val foldStateProvider: FoldStateProvider) override fun get(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float = provider.transitionProgress } + + private fun assertInProgressThread() { + check(progressHandler.looper.isCurrentThread) { + val progressThread = progressHandler.looper.thread + val thisThread = Thread.currentThread() + """should be called from the progress thread. + progressThread=$progressThread tid=${progressThread.id} + Thread.currentThread()=$thisThread tid=${thisThread.id}""" + .trimMargin() + } + } + + @AssistedFactory + interface Factory { + fun create( + foldStateProvider: FoldStateProvider, + handler: Handler, + ): PhysicsBasedUnfoldTransitionProgressProvider + } } private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider" diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt new file mode 100644 index 000000000000..1dffd84f8242 --- /dev/null +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.unfold.progress + +import android.os.Looper +import android.view.Choreographer +import androidx.dynamicanimation.animation.FrameCallbackScheduler +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +/** + * Scheduler that posts animation progresses on a thread different than the ui one. + * + * The following is taken from [AnimationHandler.FrameCallbackScheduler16]. It is extracted here as + * there are no guarantees which implementation the [DynamicAnimation] class would use otherwise. + * This allows classes using [DynamicAnimation] to be created in any thread, but still use the + * scheduler for a specific thread. + * + * Technically the [AssistedInject] is not needed: it's just to have a nicer factory with a + * documentation snippet instead of using a plain dagger provider. + */ +class UnfoldFrameCallbackScheduler @AssistedInject constructor() : FrameCallbackScheduler { + + private val choreographer = Choreographer.getInstance() + private val looper = + Looper.myLooper() ?: error("This should be created in a thread with a looper.") + + override fun postFrameCallback(frameCallback: Runnable) { + choreographer.postFrameCallback { frameCallback.run() } + } + + override fun isCurrentThread(): Boolean { + return looper.isCurrentThread + } + + @AssistedFactory + interface Factory { + /** + * Creates a [FrameCallbackScheduler] that uses [Choreographer] to post frame callbacks. + * + * Note that the choreographer used depends on the thread this [create] is called on, as it + * is get from a thread static attribute. + */ + fun create(): UnfoldFrameCallbackScheduler + } +} diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 003013e18583..77f637bb8ba1 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -23,37 +23,34 @@ import androidx.annotation.VisibleForTesting import androidx.core.util.Consumer import com.android.systemui.unfold.compat.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP import com.android.systemui.unfold.config.UnfoldTransitionConfig -import com.android.systemui.unfold.dagger.UnfoldMain -import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate -import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener -import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.util.CurrentActivityTypeProvider import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executor -import javax.inject.Inject class DeviceFoldStateProvider -@Inject +@AssistedInject constructor( config: UnfoldTransitionConfig, - private val hingeAngleProvider: HingeAngleProvider, + private val context: Context, private val screenStatusProvider: ScreenStatusProvider, - private val foldProvider: FoldProvider, private val activityTypeProvider: CurrentActivityTypeProvider, private val unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider, - private val rotationChangeProvider: RotationChangeProvider, - private val context: Context, - @UnfoldMain private val mainExecutor: Executor, - @UnfoldMain private val handler: Handler + private val foldProvider: FoldProvider, + @Assisted private val hingeAngleProvider: HingeAngleProvider, + @Assisted private val rotationChangeProvider: RotationChangeProvider, + @Assisted private val progressHandler: Handler, ) : FoldStateProvider { + private val outputListeners = CopyOnWriteArrayList<FoldStateProvider.FoldUpdatesListener>() - private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf() - - @FoldUpdate private var lastFoldUpdate: Int? = null + @FoldStateProvider.FoldUpdate private var lastFoldUpdate: Int? = null @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngleBeforeTransition: Float = 0f @@ -61,11 +58,9 @@ constructor( private val hingeAngleListener = HingeAngleListener() private val screenListener = ScreenStatusListener() private val foldStateListener = FoldStateListener() - private val mainLooper = handler.looper private val timeoutRunnable = Runnable { cancelAnimation() } - private val rotationListener = RotationListener { - if (isTransitionInProgress) cancelAnimation() - } + private val rotationListener = FoldRotationListener() + private val progressExecutor = Executor { progressHandler.post(it) } /** * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a @@ -80,9 +75,9 @@ constructor( private var isStarted = false override fun start() { - assertMainThread() if (isStarted) return - foldProvider.registerCallback(foldStateListener, mainExecutor) + foldProvider.registerCallback(foldStateListener, progressExecutor) + // TODO(b/277879146): get callbacks in the background screenStatusProvider.addCallback(screenListener) hingeAngleProvider.addCallback(hingeAngleListener) rotationChangeProvider.addCallback(rotationListener) @@ -91,7 +86,6 @@ constructor( } override fun stop() { - assertMainThread() screenStatusProvider.removeCallback(screenListener) foldProvider.unregisterCallback(foldStateListener) hingeAngleProvider.removeCallback(hingeAngleListener) @@ -101,11 +95,11 @@ constructor( isStarted = false } - override fun addCallback(listener: FoldUpdatesListener) { + override fun addCallback(listener: FoldStateProvider.FoldUpdatesListener) { outputListeners.add(listener) } - override fun removeCallback(listener: FoldUpdatesListener) { + override fun removeCallback(listener: FoldStateProvider.FoldUpdatesListener) { outputListeners.remove(listener) } @@ -121,6 +115,7 @@ constructor( lastFoldUpdate == FOLD_UPDATE_START_CLOSING private fun onHingeAngle(angle: Float) { + assertInProgressThread() if (DEBUG) { Log.d( TAG, @@ -131,14 +126,14 @@ constructor( } val currentDirection = - if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING + if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING if (isTransitionInProgress && currentDirection != lastFoldUpdate) { lastHingeAngleBeforeTransition = lastHingeAngle } val isClosing = angle < lastHingeAngleBeforeTransition val transitionUpdate = - if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING + if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING val angleChangeSurpassedThreshold = Math.abs(angle - lastHingeAngleBeforeTransition) > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES @@ -150,12 +145,12 @@ constructor( angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle eventNotAlreadyDispatched && // we haven't sent transition event already !isFullyOpened && // do not send transition event if we are in fully opened hinge - // angle range as closing threshold could overlap this range + // angle range as closing threshold could overlap this range screenAvailableEventSent && // do not send transition event if we are still in the - // process of turning on the inner display + // process of turning on the inner display isClosingThresholdMet(angle) && // hinge angle is below certain threshold. isOnLargeScreen // Avoids sending closing event when on small screen. - // Start event is sent regardless due to hall sensor. + // Start event is sent regardless due to hall sensor. ) { notifyFoldUpdate(transitionUpdate, lastHingeAngle) } @@ -202,6 +197,7 @@ constructor( private inner class FoldStateListener : FoldProvider.FoldCallback { override fun onFoldUpdated(isFolded: Boolean) { + assertInProgressThread() this@DeviceFoldStateProvider.isFolded = isFolded lastHingeAngle = FULLY_CLOSED_DEGREES @@ -218,7 +214,14 @@ constructor( } } - private fun notifyFoldUpdate(@FoldUpdate update: Int, angle: Float) { + private inner class FoldRotationListener : RotationChangeProvider.RotationListener { + override fun onRotationChanged(newRotation: Int) { + assertInProgressThread() + if (isTransitionInProgress) cancelAnimation() + } + } + + private fun notifyFoldUpdate(@FoldStateProvider.FoldUpdate update: Int, angle: Float) { if (DEBUG) { Log.d(TAG, update.name()) } @@ -236,11 +239,11 @@ constructor( if (isTransitionInProgress) { cancelTimeout() } - handler.postDelayed(timeoutRunnable, halfOpenedTimeoutMillis.toLong()) + progressHandler.postDelayed(timeoutRunnable, halfOpenedTimeoutMillis.toLong()) } private fun cancelTimeout() { - handler.removeCallbacks(timeoutRunnable) + progressHandler.removeCallbacks(timeoutRunnable) } private fun cancelAnimation(): Unit = @@ -249,42 +252,61 @@ constructor( private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener { override fun onScreenTurnedOn() { - // Trigger this event only if we are unfolded and this is the first screen - // turned on event since unfold started. This prevents running the animation when - // turning on the internal display using the power button. - // Initially isUnfoldHandled is true so it will be reset to false *only* when we - // receive 'folded' event. If SystemUI started when device is already folded it will - // still receive 'folded' event on startup. - if (!isFolded && !isUnfoldHandled) { - outputListeners.forEach { it.onUnfoldedScreenAvailable() } - isUnfoldHandled = true + executeInProgressThread { + // Trigger this event only if we are unfolded and this is the first screen + // turned on event since unfold started. This prevents running the animation when + // turning on the internal display using the power button. + // Initially isUnfoldHandled is true so it will be reset to false *only* when we + // receive 'folded' event. If SystemUI started when device is already folded it will + // still receive 'folded' event on startup. + if (!isFolded && !isUnfoldHandled) { + outputListeners.forEach { it.onUnfoldedScreenAvailable() } + isUnfoldHandled = true + } } } override fun markScreenAsTurnedOn() { - if (!isFolded) { - isUnfoldHandled = true + executeInProgressThread { + if (!isFolded) { + isUnfoldHandled = true + } } } override fun onScreenTurningOn() { - isScreenOn = true - updateHingeAngleProviderState() + executeInProgressThread { + isScreenOn = true + updateHingeAngleProviderState() + } } override fun onScreenTurningOff() { - isScreenOn = false - updateHingeAngleProviderState() + executeInProgressThread { + isScreenOn = false + updateHingeAngleProviderState() + } + } + + /** + * Needed just for compatibility while not all data sources are providing data in the + * background. + * + * TODO(b/277879146): Remove once ScreeStatusProvider provides in the background. + */ + private fun executeInProgressThread(f: () -> Unit) { + progressHandler.post { f() } } } private fun isOnLargeScreen(): Boolean { - return context.resources.configuration.smallestScreenWidthDp > - INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + return context.resources.configuration.smallestScreenWidthDp > + INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP } /** While the screen is off or the device is folded, hinge angle updates are not needed. */ private fun updateHingeAngleProviderState() { + assertInProgressThread() if (isScreenOn && !isFolded) { hingeAngleProvider.start() } else { @@ -294,20 +316,34 @@ constructor( private inner class HingeAngleListener : Consumer<Float> { override fun accept(angle: Float) { + assertInProgressThread() onHingeAngle(angle) } } - private fun assertMainThread() { - check(mainLooper.isCurrentThread) { - ("should be called from the main thread." + - " sMainLooper.threadName=" + mainLooper.thread.name + - " Thread.currentThread()=" + Thread.currentThread().name) + private fun assertInProgressThread() { + check(progressHandler.looper.isCurrentThread) { + val progressThread = progressHandler.looper.thread + val thisThread = Thread.currentThread() + """should be called from the progress thread. + progressThread=$progressThread tid=${progressThread.id} + Thread.currentThread()=$thisThread tid=${thisThread.id}""" + .trimMargin() } } + + @AssistedFactory + interface Factory { + /** Creates a [DeviceFoldStateProvider] using the provided dependencies. */ + fun create( + hingeAngleProvider: HingeAngleProvider, + rotationChangeProvider: RotationChangeProvider, + progressHandler: Handler, + ): DeviceFoldStateProvider + } } -fun @receiver:FoldUpdate Int.name() = +fun @receiver:FoldStateProvider.FoldUpdate Int.name() = when (this) { FOLD_UPDATE_START_OPENING -> "START_OPENING" FOLD_UPDATE_START_CLOSING -> "START_CLOSING" diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt index ce8f1a178d05..82ea362e8049 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt @@ -20,20 +20,21 @@ import android.content.Context import android.hardware.display.DisplayManager import android.os.Handler import android.os.RemoteException -import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.util.CallbackController -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject /** - * Allows to subscribe to rotation changes. Updates are provided for the display associated - * to [context]. + * Allows to subscribe to rotation changes. Updates are provided for the display associated to + * [context]. */ class RotationChangeProvider -@Inject +@AssistedInject constructor( private val displayManager: DisplayManager, private val context: Context, - @UnfoldMain private val mainHandler: Handler, + @Assisted private val handler: Handler, ) : CallbackController<RotationChangeProvider.RotationListener> { private val listeners = mutableListOf<RotationListener>() @@ -42,7 +43,7 @@ constructor( private var lastRotation: Int? = null override fun addCallback(listener: RotationListener) { - mainHandler.post { + handler.post { if (listeners.isEmpty()) { subscribeToRotation() } @@ -51,7 +52,7 @@ constructor( } override fun removeCallback(listener: RotationListener) { - mainHandler.post { + handler.post { listeners -= listener if (listeners.isEmpty()) { unsubscribeToRotation() @@ -62,7 +63,7 @@ constructor( private fun subscribeToRotation() { try { - displayManager.registerDisplayListener(displayListener, mainHandler) + displayManager.registerDisplayListener(displayListener, handler) } catch (e: RemoteException) { throw e.rethrowFromSystemServer() } @@ -100,4 +101,10 @@ constructor( override fun onDisplayRemoved(displayId: Int) {} } + + @AssistedFactory + interface Factory { + /** Creates a new [RotationChangeProvider] that provides updated using [handler]. */ + fun create(handler: Handler): RotationChangeProvider + } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt index 89fb12e313ec..14c4cc0aeecc 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt @@ -18,21 +18,26 @@ import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager +import android.os.Handler import android.os.Trace import androidx.core.util.Consumer import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executor -import javax.inject.Inject internal class HingeSensorAngleProvider -@Inject +@AssistedInject constructor( private val sensorManager: SensorManager, - @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor + @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor, + @Assisted private val listenerHandler: Handler, ) : HingeAngleProvider { private val sensorListener = HingeAngleSensorListener() - private val listeners: MutableList<Consumer<Float>> = arrayListOf() + private val listeners: MutableList<Consumer<Float>> = CopyOnWriteArrayList() var started = false override fun start() { @@ -43,7 +48,8 @@ constructor( sensorManager.registerListener( sensorListener, sensor, - SensorManager.SENSOR_DELAY_FASTEST + SensorManager.SENSOR_DELAY_FASTEST, + listenerHandler ) Trace.endSection() @@ -75,4 +81,10 @@ constructor( listeners.forEach { it.accept(event.values[0]) } } } + + @AssistedFactory + interface Factory { + /** Creates an [HingeSensorAngleProvider] that sends updates using [handler]. */ + fun create(handler: Handler): HingeSensorAngleProvider + } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt index d8bc01804f14..a31896a96a2b 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt @@ -16,7 +16,9 @@ package com.android.systemui.unfold.util import android.os.Trace import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import javax.inject.Qualifier /** @@ -26,11 +28,11 @@ import javax.inject.Qualifier * for each fold/unfold: in (1) systemui and (2) launcher process. */ class ATraceLoggerTransitionProgressListener -@Inject -internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String) : +@AssistedInject +internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String, @Assisted details: String) : TransitionProgressListener { - private val traceName = "$tracePrefix#$UNFOLD_TRANSITION_TRACE_NAME" + private val traceName = "$tracePrefix$details#$UNFOLD_TRANSITION_TRACE_NAME" override fun onTransitionStarted() { Trace.beginAsyncSection(traceName, /* cookie= */ 0) @@ -43,6 +45,12 @@ internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String) : override fun onTransitionProgress(progress: Float) { Trace.setCounter(traceName, (progress * 100).toLong()) } + + @AssistedFactory + interface Factory { + /** Creates an [ATraceLoggerTransitionProgressListener] with [details] in the track name. */ + fun create(details: String): ATraceLoggerTransitionProgressListener + } } private const val UNFOLD_TRANSITION_TRACE_NAME = "FoldUnfoldTransitionInProgress" diff --git a/packages/VpnDialogs/res/values-da/strings.xml b/packages/VpnDialogs/res/values-da/strings.xml index 63a32f9c113e..2d55a0630cc8 100644 --- a/packages/VpnDialogs/res/values-da/strings.xml +++ b/packages/VpnDialogs/res/values-da/strings.xml @@ -31,7 +31,7 @@ <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string> <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Skift VPN-indstillinger"</string> <string name="configure" msgid="4905518375574791375">"Konfigurer"</string> - <string name="disconnect" msgid="971412338304200056">"Fjern tilknytning"</string> + <string name="disconnect" msgid="971412338304200056">"Afbryd forbindelse"</string> <string name="open_app" msgid="3717639178595958667">"Åbn app"</string> <string name="dismiss" msgid="6192859333764711227">"Luk"</string> <string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string> diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 3f46ab859bf9..e013a3e41896 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -34,6 +34,7 @@ java_library { "framework-minus-apex.ravenwood", "junit", ], + sdk_version: "core_current", visibility: ["//frameworks/base"], } diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt index b92663c83889..c70c171048e7 100644 --- a/ravenwood/framework-minus-apex-ravenwood-policies.txt +++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt @@ -78,6 +78,7 @@ class android.util.Patterns stubclass class android.util.UtilConfig stubclass # Internals +class com.android.internal.util.FastMath stubclass class com.android.internal.util.FastPrintWriter stubclass class com.android.internal.util.GrowingArrayUtils stubclass class com.android.internal.util.LineBreakBufferedWriter stubclass diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt index e943786a5369..07c2cd7c26b3 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -51,3 +51,11 @@ android.database.Observable android.text.TextUtils android.text.TextUtils$SimpleStringSplitter + +android.accounts.Account + +android.graphics.Insets +android.graphics.Point +android.graphics.PointF +android.graphics.Rect +android.graphics.RectF diff --git a/services/Android.bp b/services/Android.bp index 02a7a78653aa..5cb8ec628c38 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -140,6 +140,7 @@ filegroup { ":services.voiceinteraction-sources", ":services.wallpapereffectsgeneration-sources", ":services.wifi-sources", + ":framework-pm-common-shared-srcs", ], visibility: ["//visibility:private"], } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 041cd75f82e7..71878954e9ec 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -34,7 +34,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCE import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; -import static com.android.window.flags.Flags.removeCaptureDisplay; +import static com.android.window.flags.Flags.deleteCaptureDisplay; import android.accessibilityservice.AccessibilityGestureEvent; import android.accessibilityservice.AccessibilityService; @@ -1443,7 +1443,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return; } final long identity = Binder.clearCallingIdentity(); - if (removeCaptureDisplay()) { + if (deleteCaptureDisplay()) { try { ScreenCapture.ScreenCaptureListener screenCaptureListener = new ScreenCapture.ScreenCaptureListener( @@ -1485,7 +1485,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private void sendScreenshotSuccess(ScreenshotHardwareBuffer screenshotBuffer, RemoteCallback callback) { - if (removeCaptureDisplay()) { + if (deleteCaptureDisplay()) { mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); final ParcelableColorSpace colorSpace = diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS index 5295ec82e3c3..4fe0592f9075 100644 --- a/services/companion/java/com/android/server/companion/virtual/OWNERS +++ b/services/companion/java/com/android/server/companion/virtual/OWNERS @@ -2,7 +2,7 @@ set noparent -ogunwale@google.com -michaelwr@google.com +marvinramin@google.com vladokom@google.com -marvinramin@google.com
\ No newline at end of file +ogunwale@google.com +michaelwr@google.com
\ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index e6bfeb79fafb..4987fbce42d0 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -39,7 +39,6 @@ import android.app.Activity; import android.app.ActivityOptions; import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; -import android.app.compat.CompatChanges; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; @@ -55,8 +54,6 @@ import android.companion.virtual.camera.VirtualCameraConfig; import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorEvent; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; import android.content.AttributionSource; import android.content.ComponentName; import android.content.Context; @@ -81,7 +78,6 @@ import android.hardware.input.VirtualNavigationTouchpadConfig; import android.hardware.input.VirtualTouchEvent; import android.hardware.input.VirtualTouchscreenConfig; import android.os.Binder; -import android.os.Build; import android.os.IBinder; import android.os.LocaleList; import android.os.Looper; @@ -122,22 +118,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private static final String TAG = "VirtualDeviceImpl"; - /** - * Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent - * with virtual displays created via {@link android.hardware.display.DisplayManager} and allow - * for the creation of private, auto-mirror, and fixed orientation displays since - * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}. - * - * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC - * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY - * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR - * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT - */ - @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - public static final long MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER = - 294837146L; - private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL @@ -365,8 +345,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; - if (!CompatChanges.isChangeEnabled( - MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER, mOwnerUid)) { + if (!Flags.consistentDisplayFlags()) { flags |= DEFAULT_VIRTUAL_DISPLAY_FLAGS_PRE_VIC; } if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) { 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 e51ef297519f..d194bb29b19b 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -187,9 +187,7 @@ public class VirtualDeviceManagerService extends SystemService { CompanionDeviceManager cdm = getContext().getSystemService(CompanionDeviceManager.class); if (cdm != null) { - synchronized (mVirtualDeviceManagerLock) { - mActiveAssociations = cdm.getAllAssociations(UserHandle.USER_ALL); - } + onCdmAssociationsChanged(cdm.getAllAssociations(UserHandle.USER_ALL)); cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(), this::onCdmAssociationsChanged, UserHandle.USER_ALL); } else { @@ -345,19 +343,21 @@ public class VirtualDeviceManagerService extends SystemService { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) void onCdmAssociationsChanged(List<AssociationInfo> associations) { + List<AssociationInfo> vdmAssociations = new ArrayList<>(); + Set<Integer> activeAssociationIds = new HashSet<>(); + for (int i = 0; i < associations.size(); ++i) { + AssociationInfo association = associations.get(i); + if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(association.getDeviceProfile())) { + vdmAssociations.add(association); + activeAssociationIds.add(association.getId()); + } + } Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>(); Set<String> removedPersistentDeviceIds = new HashSet<>(); synchronized (mVirtualDeviceManagerLock) { - Set<Integer> activeAssociationIds = new HashSet<>(associations.size()); - for (int i = 0; i < associations.size(); ++i) { - activeAssociationIds.add(associations.get(i).getId()); - } - for (int i = 0; i < mActiveAssociations.size(); ++i) { AssociationInfo associationInfo = mActiveAssociations.get(i); - if (!activeAssociationIds.contains(associationInfo.getId()) - && VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains( - associationInfo.getDeviceProfile())) { + if (!activeAssociationIds.contains(associationInfo.getId())) { removedPersistentDeviceIds.add( VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId())); } @@ -370,7 +370,7 @@ public class VirtualDeviceManagerService extends SystemService { } } - mActiveAssociations = associations; + mActiveAssociations = vdmAssociations; } for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) { @@ -869,12 +869,8 @@ public class VirtualDeviceManagerService extends SystemService { 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())); - } + persistentIds.add( + VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId())); } } return persistentIds; diff --git a/services/core/Android.bp b/services/core/Android.bp index 49457fbb94f5..3323d0ba64dd 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -125,6 +125,9 @@ java_library_static { "java/com/android/server/am/EventLogTags.logtags", "java/com/android/server/wm/EventLogTags.logtags", "java/com/android/server/policy/EventLogTags.logtags", + + // Java/AIDL sources to be moved out to CrashRecovery module + ":services-crashrecovery-sources", ], libs: [ diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 7e4cf4f35132..898b69310fe1 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -48,6 +48,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.permission.persistence.RuntimePermissionsState; import com.android.server.pm.Installer.LegacyDexoptDisabledException; import com.android.server.pm.KnownPackages; +import com.android.server.pm.PackageArchiver; import com.android.server.pm.PackageList; import com.android.server.pm.PackageSetting; import com.android.server.pm.dex.DynamicCodeLogger; @@ -1442,4 +1443,10 @@ public abstract class PackageManagerInternal { */ public abstract void sendPackageDataClearedBroadcast(@NonNull String packageName, int uid, int userId, boolean isRestore, boolean isInstantApp); + + /** + * Returns an instance of {@link PackageArchiver} to be used for archiving related operations. + */ + @NonNull + public abstract PackageArchiver getPackageArchiver(); } diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index 987507fe7f03..7fe06827f1b3 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -35,7 +35,7 @@ per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo per-file MmsServiceBroker.java = file:/telephony/OWNERS per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS -per-file PinnerService.java = file:/apct-tests/perftests/OWNERS +per-file PinnerService.java = file:/core/java/android/app/pinner/OWNERS per-file RescueParty.java = shuc@google.com, ancr@google.com, harshitmahajan@google.com per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index ce1a8756a849..23a30f9dc2ee 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 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. @@ -20,6 +20,9 @@ import static android.app.ActivityManager.UID_OBSERVER_ACTIVE; import static android.app.ActivityManager.UID_OBSERVER_GONE; import static android.os.Process.SYSTEM_UID; +import static com.android.server.flags.Flags.pinWebview; + +import android.annotation.EnforcePermission; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +31,8 @@ import android.app.ActivityManagerInternal; import android.app.IActivityManager; import android.app.SearchManager; import android.app.UidObserver; +import android.app.pinner.IPinnerService; +import android.app.pinner.PinnedFileStat; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -48,6 +53,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; @@ -83,6 +89,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -94,6 +102,7 @@ import sun.misc.Unsafe; * <p>Files to pin are specified in the config_defaultPinnerServiceFiles * overlay.</p> * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p> + * <p>(Optional) Pin experimental carveout regions based on DeviceConfig flags.</p> */ public final class PinnerService extends SystemService { private static final boolean DEBUG = false; @@ -110,16 +119,15 @@ public final class PinnerService extends SystemService { private static final int KEY_ASSISTANT = 2; // Pin using pinlist.meta when pinning apps. - private static boolean PROP_PIN_PINLIST = SystemProperties.getBoolean( - "pinner.use_pinlist", true); - // Pin the whole odex/vdex/etc file when pinning apps. - private static boolean PROP_PIN_ODEX = SystemProperties.getBoolean( - "pinner.whole_odex", true); + private static boolean PROP_PIN_PINLIST = + SystemProperties.getBoolean("pinner.use_pinlist", true); private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app. private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app. private static final int MAX_ASSISTANT_PIN_SIZE = 60 * (1 << 20); // 60MB max for assistant app. + public static final String ANON_REGION_STAT_NAME = "[anon]"; + @IntDef({KEY_CAMERA, KEY_HOME, KEY_ASSISTANT}) @Retention(RetentionPolicy.SOURCE) public @interface AppKey {} @@ -134,8 +142,7 @@ public final class PinnerService extends SystemService { private SearchManager mSearchManager; /** The list of the statically pinned files. */ - @GuardedBy("this") - private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>(); + @GuardedBy("this") private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>(); /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */ @GuardedBy("this") @@ -174,6 +181,7 @@ public final class PinnerService extends SystemService { private final boolean mConfiguredToPinCamera; private final boolean mConfiguredToPinHome; private final boolean mConfiguredToPinAssistant; + private final int mConfiguredWebviewPinBytes; private BinderService mBinderService; private PinnerHandler mPinnerHandler = null; @@ -213,6 +221,11 @@ public final class PinnerService extends SystemService { protected void publishBinderService(PinnerService service, Binder binderService) { service.publishBinderService("pinner", binderService); } + + protected PinnedFile pinFileInternal(String fileToPin, + int maxBytesToPin, boolean attemptPinIntrospection) { + return PinnerService.pinFileInternal(fileToPin, maxBytesToPin, attemptPinIntrospection); + } } public PinnerService(Context context) { @@ -232,6 +245,8 @@ public final class PinnerService extends SystemService { com.android.internal.R.bool.config_pinnerHomeApp); mConfiguredToPinAssistant = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerAssistantApp); + mConfiguredWebviewPinBytes = context.getResources().getInteger( + com.android.internal.R.integer.config_pinnerWebviewPinBytes); mPinKeys = createPinKeys(); mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper()); @@ -321,7 +336,7 @@ public final class PinnerService extends SystemService { public List<PinnedFileStats> dumpDataForStatsd() { List<PinnedFileStats> pinnedFileStats = new ArrayList<>(); synchronized (PinnerService.this) { - for (PinnedFile pinnedFile : mPinnedFiles) { + for (PinnedFile pinnedFile : mPinnedFiles.values()) { pinnedFileStats.add(new PinnedFileStats(SYSTEM_UID, pinnedFile)); } @@ -357,39 +372,17 @@ public final class PinnerService extends SystemService { com.android.internal.R.array.config_defaultPinnerServiceFiles); // Continue trying to pin each file even if we fail to pin some of them for (String fileToPin : filesToPin) { - PinnedFile pf = pinFile(fileToPin, - Integer.MAX_VALUE, - /*attemptPinIntrospection=*/false); + PinnedFile pf = mInjector.pinFileInternal(fileToPin, Integer.MAX_VALUE, + /*attemptPinIntrospection=*/false); if (pf == null) { Slog.e(TAG, "Failed to pin file = " + fileToPin); continue; } synchronized (this) { - mPinnedFiles.add(pf); - } - if (fileToPin.endsWith(".jar") | fileToPin.endsWith(".apk")) { - // Check whether the runtime has compilation artifacts to pin. - String arch = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); - String[] files = null; - try { - files = DexFile.getDexFileOutputPaths(fileToPin, arch); - } catch (IOException ioe) { } - if (files == null) { - continue; - } - for (String file : files) { - PinnedFile df = pinFile(file, - Integer.MAX_VALUE, - /*attemptPinIntrospection=*/false); - if (df == null) { - Slog.i(TAG, "Failed to pin ART file = " + file); - continue; - } - synchronized (this) { - mPinnedFiles.add(df); - } - } + mPinnedFiles.put(pf.fileName, pf); } + pf.groupName = "system"; + pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, null); } refreshPinAnonConfig(); @@ -486,7 +479,7 @@ public final class PinnerService extends SystemService { pinnedAppFiles = new ArrayList<>(app.mFiles); } for (PinnedFile pinnedFile : pinnedAppFiles) { - pinnedFile.close(); + unpinFile(pinnedFile.fileName); } } @@ -494,6 +487,19 @@ public final class PinnerService extends SystemService { return ResolverActivity.class.getName().equals(info.name); } + public int getWebviewPinQuota() { + if (!pinWebview()) { + return 0; + } + int quota = mConfiguredWebviewPinBytes; + int overrideQuota = SystemProperties.getInt("pinner.pin_webview_size", -1); + if (overrideQuota != -1) { + // Quota was overridden + quota = overrideQuota; + } + return quota; + } + private ApplicationInfo getCameraInfo(int userHandle) { Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle, @@ -727,7 +733,7 @@ public final class PinnerService extends SystemService { case KEY_ASSISTANT: return "Assistant"; default: - return null; + return ""; } } @@ -867,11 +873,12 @@ public final class PinnerService extends SystemService { continue; } - PinnedFile pf = pinFile(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true); + PinnedFile pf = mInjector.pinFileInternal(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true); if (pf == null) { Slog.e(TAG, "Failed to pin " + apk); continue; } + pf.groupName = getNameForKey(key); if (DEBUG) { Slog.i(TAG, "Pinned " + pf.fileName); @@ -881,40 +888,118 @@ public final class PinnerService extends SystemService { } apkPinSizeLimit -= pf.bytesPinned; + if (apk.equals(appInfo.sourceDir)) { + pinOptimizedDexDependencies(pf, apkPinSizeLimit, appInfo); + } } + } - // determine the ABI from either ApplicationInfo or Build - String abi = appInfo.primaryCpuAbi != null ? appInfo.primaryCpuAbi : - Build.SUPPORTED_ABIS[0]; - String arch = VMRuntime.getInstructionSet(abi); - // get the path to the odex or oat file - String baseCodePath = appInfo.getBaseCodePath(); - String[] files = null; - try { - files = DexFile.getDexFileOutputPaths(baseCodePath, arch); - } catch (IOException ioe) {} - if (files == null) { - return; + /** + * Pin file or apk to memory. + * + * Prefer to use this method instead of {@link #pinFileInternal(String, int, boolean)} as it + * takes care of accounting and if pinning an apk, it also pins any extra optimized art files + * that related to the file but not within itself. + * + * @param fileToPin File to pin + * @param maxBytesToPin maximum quota allowed for pinning + * @return total bytes that were pinned. + */ + public int pinFile(String fileToPin, int maxBytesToPin, @Nullable ApplicationInfo appInfo, + @Nullable String groupName) { + PinnedFile existingPin; + synchronized(this) { + existingPin = mPinnedFiles.get(fileToPin); + } + if (existingPin != null) { + if (existingPin.bytesPinned == maxBytesToPin) { + // Duplicate pin requesting same amount of bytes, lets just bail out. + return 0; + } else { + // User decided to pin a different amount of bytes than currently pinned + // so this is a valid pin request. Unpin the previous version before repining. + if (DEBUG) { + Slog.d(TAG, "Unpinning file prior to repin: " + fileToPin); + } + unpinFile(fileToPin); + } } - //not pinning the oat/odex is not a fatal error - for (String file : files) { - PinnedFile pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false); - if (pf != null) { - synchronized (this) { - if (PROP_PIN_ODEX) { - pinnedApp.mFiles.add(pf); - } + boolean isApk = fileToPin.endsWith(".apk"); + int bytesPinned = 0; + PinnedFile pf = mInjector.pinFileInternal(fileToPin, maxBytesToPin, + /*attemptPinIntrospection=*/isApk); + if (pf == null) { + Slog.e(TAG, "Failed to pin file = " + fileToPin); + return 0; + } + pf.groupName = groupName != null ? groupName : ""; + + maxBytesToPin -= bytesPinned; + bytesPinned += pf.bytesPinned; + + synchronized (this) { + mPinnedFiles.put(pf.fileName, pf); + } + if (maxBytesToPin > 0) { + pinOptimizedDexDependencies(pf, maxBytesToPin, appInfo); + } + return bytesPinned; + } + + /** + * Pin any dependency optimized files generated by ART. + * @param pinnedFile An already pinned file whose dependencies we want pinned. + * @param maxBytesToPin Maximum amount of bytes to pin. + * @param appInfo Used to determine the ABI in case the application has one custom set, when set + * to null it will use the default supported ABI by the device. + * @return total bytes pinned. + */ + private int pinOptimizedDexDependencies( + PinnedFile pinnedFile, int maxBytesToPin, @Nullable ApplicationInfo appInfo) { + if (pinnedFile == null) { + return 0; + } + + int bytesPinned = 0; + if (pinnedFile.fileName.endsWith(".jar") | pinnedFile.fileName.endsWith(".apk")) { + String abi = null; + if (appInfo != null) { + abi = appInfo.primaryCpuAbi; + } + if (abi == null) { + abi = Build.SUPPORTED_ABIS[0]; + } + // Check whether the runtime has compilation artifacts to pin. + String arch = VMRuntime.getInstructionSet(abi); + String[] files = null; + try { + files = DexFile.getDexFileOutputPaths(pinnedFile.fileName, arch); + } catch (IOException ioe) { + } + if (files == null) { + return bytesPinned; + } + for (String file : files) { + // Unpin if it was already pinned prior to re-pinning. + unpinFile(file); + + PinnedFile df = mInjector.pinFileInternal(file, Integer.MAX_VALUE, + /*attemptPinIntrospection=*/false); + if (df == null) { + Slog.i(TAG, "Failed to pin ART file = " + file); + return bytesPinned; } - if (DEBUG) { - if (PROP_PIN_ODEX) { - Slog.i(TAG, "Pinned " + pf.fileName); - } else { - Slog.i(TAG, "Pinned [skip] " + pf.fileName); - } + df.groupName = pinnedFile.groupName; + pinnedFile.pinnedDeps.add(df); + maxBytesToPin -= df.bytesPinned; + bytesPinned += df.bytesPinned; + synchronized (this) { + mPinnedFiles.put(df.fileName, df); } } } + return bytesPinned; } /** mlock length bytes of fileToPin in memory @@ -954,9 +1039,12 @@ public final class PinnerService extends SystemService { * zip in order to extract the * @return Pinned memory resource owner thing or null on error */ - private static PinnedFile pinFile(String fileToPin, - int maxBytesToPin, - boolean attemptPinIntrospection) { + private static PinnedFile pinFileInternal( + String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection) { + if (DEBUG) { + Slog.d(TAG, "pin file: " + fileToPin + " use-pinlist: " + attemptPinIntrospection); + } + Trace.beginSection("pinFile:" + fileToPin); ZipFile fileAsZip = null; InputStream pinRangeStream = null; try { @@ -967,16 +1055,19 @@ public final class PinnerService extends SystemService { if (fileAsZip != null) { pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin); } - - Slog.d(TAG, "pinRangeStream: " + pinRangeStream); - - PinRangeSource pinRangeSource = (pinRangeStream != null) - ? new PinRangeSourceStream(pinRangeStream) - : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */); - return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource); + boolean use_pinlist = (pinRangeStream != null); + PinRangeSource pinRangeSource = use_pinlist + ? new PinRangeSourceStream(pinRangeStream) + : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */); + PinnedFile pinnedFile = pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource); + if (pinnedFile != null) { + pinnedFile.used_pinlist = use_pinlist; + } + return pinnedFile; } finally { safeClose(pinRangeStream); safeClose(fileAsZip); // Also closes any streams we've opened + Trace.endSection(); } } @@ -1012,9 +1103,23 @@ public final class PinnerService extends SystemService { return null; } + // Looking at root directory is the old behavior but still some apps rely on it so keeping + // for backward compatibility. As doing a single item lookup is cheap in the root. ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME); + + if (pinMetaEntry == null) { + // It is usually within an apk's control to include files in assets/ directory + // so this would be the expected point to have the pinlist.meta coming from. + // we explicitly avoid doing an exhaustive search because it may be expensive so + // prefer to have a good known location to retrieve the file. + pinMetaEntry = zipFile.getEntry("assets/" + PIN_META_FILENAME); + } + InputStream pinMetaStream = null; if (pinMetaEntry != null) { + if (DEBUG) { + Slog.d(TAG, "Found pinlist.meta for " + fileName); + } try { pinMetaStream = zipFile.getInputStream(pinMetaEntry); } catch (IOException ex) { @@ -1023,6 +1128,10 @@ public final class PinnerService extends SystemService { fileName), ex); } + } else { + Slog.w(TAG, + String.format( + "Could not find pinlist.meta for \"%s\": pinning as blob", fileName)); } return pinMetaStream; } @@ -1159,6 +1268,49 @@ public final class PinnerService extends SystemService { } } } + private List<PinnedFile> getAllPinsForGroup(String group) { + List<PinnedFile> filesInGroup; + synchronized (this) { + filesInGroup = mPinnedFiles.values() + .stream() + .filter(pf -> pf.groupName.equals(group)) + .toList(); + } + return filesInGroup; + } + public void unpinGroup(String group) { + List<PinnedFile> pinnedFiles = getAllPinsForGroup(group); + for (PinnedFile pf : pinnedFiles) { + unpinFile(pf.fileName); + } + } + + public void unpinFile(String filename) { + PinnedFile pinnedFile; + synchronized (this) { + pinnedFile = mPinnedFiles.get(filename); + } + if (pinnedFile == null) { + // File not pinned, nothing to do. + return; + } + pinnedFile.close(); + synchronized (this) { + if (DEBUG) { + Slog.d(TAG, "Unpinned file: " + filename); + } + mPinnedFiles.remove(pinnedFile.fileName); + for (PinnedFile dep : pinnedFile.pinnedDeps) { + if (dep == null) { + continue; + } + mPinnedFiles.remove(dep.fileName); + if (DEBUG) { + Slog.d(TAG, "Unpinned dependency: " + dep.fileName); + } + } + } + } private static int clamp(int min, int value, int max) { return Math.max(min, Math.min(value, max)); @@ -1204,17 +1356,44 @@ public final class PinnerService extends SystemService { } } - private final class BinderService extends Binder { + public List<PinnedFileStat> getPinnerStats() { + ArrayList<PinnedFileStat> stats = new ArrayList<>(); + Collection<PinnedApp> pinnedApps; + synchronized(this) { + pinnedApps = mPinnedApps.values(); + } + for (PinnedApp pinnedApp : pinnedApps) { + for (PinnedFile pf : pinnedApp.mFiles) { + PinnedFileStat stat = + new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName); + stats.add(stat); + } + } + + Collection<PinnedFile> pinnedFiles; + synchronized(this) { + pinnedFiles = mPinnedFiles.values(); + } + for (PinnedFile pf : pinnedFiles) { + PinnedFileStat stat = new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName); + stats.add(stat); + } + if (mCurrentlyPinnedAnonSize > 0) { + stats.add(new PinnedFileStat(ANON_REGION_STAT_NAME, + mCurrentlyPinnedAnonSize, ANON_REGION_STAT_NAME)); + } + return stats; + } + + public final class BinderService extends IPinnerService.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + HashSet<PinnedFile> shownPins = new HashSet<>(); + HashSet<String> groups = new HashSet<>(); + final int bytesPerMB = 1024 * 1024; synchronized (PinnerService.this) { long totalSize = 0; - for (PinnedFile pinnedFile : mPinnedFiles) { - pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned); - totalSize += pinnedFile.bytesPinned; - } - pw.println(); for (int key : mPinnedApps.keySet()) { PinnedApp app = mPinnedApps.get(key); pw.print(getNameForKey(key)); @@ -1222,14 +1401,53 @@ public final class PinnerService extends SystemService { pw.print(" active="); pw.print(app.active); pw.println(); for (PinnedFile pf : mPinnedApps.get(key).mFiles) { - pw.print(" "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned); + pw.print(" "); + pw.format("%s pinned:%d bytes (%d MB) pinlist:%b\n", pf.fileName, + pf.bytesPinned, pf.bytesPinned / bytesPerMB, pf.used_pinlist); totalSize += pf.bytesPinned; + shownPins.add(pf); + for (PinnedFile dep : pf.pinnedDeps) { + pw.print(" "); + pw.format("%s pinned:%d bytes (%d MB) pinlist:%b (Dependency)\n", dep.fileName, + dep.bytesPinned, dep.bytesPinned / bytesPerMB, dep.used_pinlist); + totalSize += dep.bytesPinned; + shownPins.add(dep); + } + } + } + pw.println(); + for (PinnedFile pinnedFile : mPinnedFiles.values()) { + if (!groups.contains(pinnedFile.groupName)) { + groups.add(pinnedFile.groupName); } } + boolean firstPinInGroup = true; + for (String group : groups) { + List<PinnedFile> groupPins = getAllPinsForGroup(group); + for (PinnedFile pinnedFile : groupPins) { + if (shownPins.contains(pinnedFile)) { + // Already showed in the dump and accounted for, skip. + continue; + } + if (firstPinInGroup) { + firstPinInGroup = false; + // Ensure we only print when there are pins for groups not yet shown + // in the pinned app section. + pw.print("Group:" + group); + pw.println(); + } + pw.format(" %s pinned:%d bytes (%d MB) pinlist:%b\n", pinnedFile.fileName, + pinnedFile.bytesPinned, pinnedFile.bytesPinned / bytesPerMB, + pinnedFile.used_pinlist); + totalSize += pinnedFile.bytesPinned; + } + } + pw.println(); if (mPinAnonAddress != 0) { - pw.format("Pinned anon region: %s\n", mCurrentlyPinnedAnonSize); + pw.format("Pinned anon region: %d (%d MB)\n", mCurrentlyPinnedAnonSize, mCurrentlyPinnedAnonSize / bytesPerMB); + totalSize += mCurrentlyPinnedAnonSize; } - pw.format("Total size: %s\n", totalSize); + pw.format("Total pinned: %s bytes (%s MB)\n", totalSize, totalSize / bytesPerMB); pw.println(); if (!mPendingRepin.isEmpty()) { pw.print("Pending repin: "); @@ -1276,14 +1494,29 @@ public final class PinnerService extends SystemService { resultReceiver.send(0, null); } + + @EnforcePermission(android.Manifest.permission.DUMP) + @Override + public List<PinnedFileStat> getPinnerStats() { + getPinnerStats_enforcePermission(); + return PinnerService.this.getPinnerStats(); + } } - private static final class PinnedFile implements AutoCloseable { + @VisibleForTesting + public static final class PinnedFile implements AutoCloseable { private long mAddress; final int mapSize; final String fileName; final int bytesPinned; + // Whether this file was pinned using a pinlist + boolean used_pinlist; + + // User defined group name for pinner accounting + String groupName = ""; + ArrayList<PinnedFile> pinnedDeps = new ArrayList<>(); + PinnedFile(long address, int mapSize, String fileName, int bytesPinned) { mAddress = address; this.mapSize = mapSize; @@ -1297,6 +1530,11 @@ public final class PinnerService extends SystemService { safeMunmap(mAddress, mapSize); mAddress = -1; } + for (PinnedFile dep : pinnedDeps) { + if (dep != null) { + dep.close(); + } + } } @Override @@ -1354,5 +1592,4 @@ public final class PinnerService extends SystemService { } } } - } diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 80c4c58cea97..708da19232e1 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -71,6 +71,18 @@ "file_patterns": ["BinaryTransparencyService\\.java"] }, { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.PinnerServiceTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ], + "file_patterns": ["PinnerService\\.java"] + }, + { "name": "BinaryTransparencyHostTest", "file_patterns": ["BinaryTransparencyService\\.java"] }, @@ -139,6 +151,18 @@ }, { "name": "CtsSuspendAppsTestCases" + }, + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.PinnerServiceTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ], + "file_patterns": ["PinnerService\\.java"] } ] } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c64fb2366dd6..54c8ed38bb1c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4852,8 +4852,10 @@ public class ActivityManagerService extends IActivityManager.Stub } else { Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid + ". Uid: " + uid); - killProcess(pid); - killProcessGroup(uid, pid); + if (pid > 0) { + killProcess(pid); + killProcessGroup(uid, pid); + } mProcessList.noteAppKill(pid, uid, ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, ApplicationExitInfo.SUBREASON_UNKNOWN, diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 36356bd95128..e65603021708 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -120,12 +120,15 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.power.optimization.Flags; import com.android.server.power.stats.AggregatedPowerStatsConfig; import com.android.server.power.stats.BatteryExternalStatsWorker; +import com.android.server.power.stats.BatteryStatsDumpHelperImpl; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.power.stats.BatteryUsageStatsProvider; import com.android.server.power.stats.CpuAggregatedPowerStatsProcessor; import com.android.server.power.stats.PowerStatsAggregator; +import com.android.server.power.stats.PowerStatsExporter; import com.android.server.power.stats.PowerStatsScheduler; import com.android.server.power.stats.PowerStatsStore; +import com.android.server.power.stats.PowerStatsUidResolver; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; import com.android.server.power.stats.wakeups.CpuWakeupStats; @@ -181,6 +184,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub private final BatteryExternalStatsWorker mWorker; private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; private final AtomicFile mConfigFile; + private final BatteryStats.BatteryStatsDumpHelper mDumpHelper; + private final PowerStatsUidResolver mPowerStatsUidResolver; private volatile boolean mMonitorEnabled = true; @@ -408,9 +413,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge) .setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu) .build(); + mPowerStatsUidResolver = new PowerStatsUidResolver(); mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile, - mCpuScalingPolicies); + mCpuScalingPolicies, mPowerStatsUidResolver); mWorker = new BatteryExternalStatsWorker(context, mStats); mStats.setExternalStatsSyncLocked(mWorker); mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger( @@ -419,8 +425,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub AggregatedPowerStatsConfig aggregatedPowerStatsConfig = getAggregatedPowerStatsConfig(); mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig); - mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats, - mPowerStatsStore); mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig, mStats.getHistory()); final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger( @@ -429,7 +433,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub com.android.internal.R.integer.config_powerStatsAggregationPeriod); mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore, - Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats, mBatteryUsageStatsProvider); + Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats); + PowerStatsExporter powerStatsExporter = + new PowerStatsExporter(mPowerStatsStore, mPowerStatsAggregator); + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, + powerStatsExporter, mPowerProfile, mCpuScalingPolicies, + mPowerStatsStore, Clock.SYSTEM_CLOCK); + mStats.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, mPowerStatsStore); + mDumpHelper = new BatteryStatsDumpHelperImpl(mBatteryUsageStatsProvider); mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler); mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config")); } @@ -469,9 +480,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub } public void systemServicesReady() { + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(Flags.streamlinedBatteryStats()); + mWorker.systemServicesReady(); mStats.systemServicesReady(mContext); mCpuWakeupStats.systemServicesReady(); - mWorker.systemServicesReady(); final INetworkManagementService nms = INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); @@ -775,25 +787,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub } void addIsolatedUid(final int isolatedUid, final int appUid) { - synchronized (mLock) { - final long elapsedRealtime = SystemClock.elapsedRealtime(); - final long uptime = SystemClock.uptimeMillis(); - mHandler.post(() -> { - synchronized (mStats) { - mStats.addIsolatedUidLocked(isolatedUid, appUid, elapsedRealtime, uptime); - } - }); - } + mPowerStatsUidResolver.noteIsolatedUidAdded(isolatedUid, appUid); + FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, + FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED); } void removeIsolatedUid(final int isolatedUid, final int appUid) { - synchronized (mLock) { - mHandler.post(() -> { - synchronized (mStats) { - mStats.scheduleRemoveIsolatedUidLocked(isolatedUid, appUid); - } - }); - } + mPowerStatsUidResolver.noteIsolatedUidRemoved(isolatedUid, appUid); + FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, -1, isolatedUid, + FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED); } void noteProcessStart(final String name, final int uid) { @@ -877,12 +879,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub awaitCompletion(); - if (mBatteryUsageStatsProvider.shouldUpdateStats(queries, + if (BatteryUsageStatsProvider.shouldUpdateStats(queries, + SystemClock.elapsedRealtime(), mWorker.getLastCollectionTimeStamp())) { syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL); } - return mBatteryUsageStatsProvider.getBatteryUsageStats(queries); + synchronized (mStats) { + return mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, queries); + } } /** Register callbacks for statsd pulled atoms. */ @@ -2723,7 +2728,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub synchronized (mStats) { mStats.prepareForDumpLocked(); BatteryUsageStats batteryUsageStats = - mBatteryUsageStatsProvider.getBatteryUsageStats(query); + mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query); if (proto) { batteryUsageStats.dumpToProto(fd); } else { @@ -3008,11 +3013,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, null, mStats.mHandler, null, null, mUserManagerUserInfoProvider, mPowerProfile, - mCpuScalingPolicies); + mCpuScalingPolicies, null); checkinStats.readSummaryFromParcel(in); in.recycle(); - checkinStats.dumpProtoLocked( - mContext, fd, apps, flags, historyStart); + checkinStats.dumpProtoLocked(mContext, fd, apps, flags, + historyStart, mDumpHelper); mStats.mCheckinFile.delete(); return; } @@ -3026,7 +3031,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub if (DBG) Slog.d(TAG, "begin dumpProtoLocked from UID " + Binder.getCallingUid()); awaitCompletion(); synchronized (mStats) { - mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart); + mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart, mDumpHelper); if (writeData) { mStats.writeAsyncLocked(); } @@ -3050,11 +3055,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, null, mStats.mHandler, null, null, mUserManagerUserInfoProvider, mPowerProfile, - mCpuScalingPolicies); + mCpuScalingPolicies, null); checkinStats.readSummaryFromParcel(in); in.recycle(); checkinStats.dumpCheckin(mContext, pw, apps, flags, - historyStart); + historyStart, mDumpHelper); mStats.mCheckinFile.delete(); return; } @@ -3067,7 +3072,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } if (DBG) Slog.d(TAG, "begin dumpCheckin from UID " + Binder.getCallingUid()); awaitCompletion(); - mStats.dumpCheckin(mContext, pw, apps, flags, historyStart); + mStats.dumpCheckin(mContext, pw, apps, flags, historyStart, mDumpHelper); if (writeData) { mStats.writeAsyncLocked(); } @@ -3076,7 +3081,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub if (DBG) Slog.d(TAG, "begin dump from UID " + Binder.getCallingUid()); awaitCompletion(); - mStats.dump(mContext, pw, flags, reqUid, historyStart); + mStats.dump(mContext, pw, flags, reqUid, historyStart, mDumpHelper); if (writeData) { mStats.writeAsyncLocked(); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index cb2b5fbe7a5a..4ff34b1d7faa 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -34,7 +34,7 @@ import static android.os.Process.getFreeMemory; import static android.os.Process.getTotalMemory; import static android.os.Process.killProcessQuiet; import static android.os.Process.startWebView; -import static android.system.OsConstants.*; +import static android.system.OsConstants.EAGAIN; import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; @@ -133,7 +133,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ProcessMap; import com.android.internal.os.Zygote; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.MemInfoReader; import com.android.server.AppStateTracker; import com.android.server.LocalServices; @@ -3299,8 +3298,6 @@ public final class ProcessList { // about the process state of the isolated UID *before* it is registered with the // owning application. mService.mBatteryStatsService.addIsolatedUid(uid, info.uid); - FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, info.uid, uid, - FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED); } final ProcessRecord r = new ProcessRecord(mService, info, proc, uid, sdkSandboxClientAppPackage, diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java index 0ee7d9cdd3d4..091696737dcf 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java @@ -20,6 +20,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM; import static android.app.AppOpsManager.OP_USE_FULL_SCREEN_INTENT; +import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; import android.annotation.NonNull; import android.annotation.UserIdInt; @@ -150,7 +151,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface } @Override - public SparseIntArray getNonDefaultUidModes(int uid) { + public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) { synchronized (mLock) { SparseIntArray opModes = mUidModes.get(uid, null); if (opModes == null) { @@ -176,7 +177,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface } @Override - public int getUidMode(int uid, int op) { + public int getUidMode(int uid, String persistentDeviceId, int op) { synchronized (mLock) { SparseIntArray opModes = mUidModes.get(uid, null); if (opModes == null) { @@ -187,7 +188,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface } @Override - public boolean setUidMode(int uid, int op, int mode) { + public boolean setUidMode(int uid, String persistentDeviceId, int op, int mode) { final int defaultMode = AppOpsManager.opToDefaultMode(op); List<AppOpsModeChangedListener> listenersCopy; synchronized (mLock) { @@ -329,7 +330,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface } @Override - public SparseBooleanArray getForegroundOps(int uid) { + public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) { SparseBooleanArray result = new SparseBooleanArray(); synchronized (mLock) { SparseIntArray modes = mUidModes.get(uid); @@ -606,9 +607,17 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface for (final String pkg : packagesDeclaringPermission) { for (int userId : userIds) { final int uid = pmi.getPackageUid(pkg, 0, userId); - final int oldMode = getUidMode(uid, OP_SCHEDULE_EXACT_ALARM); + final int oldMode = + getUidMode( + uid, + PERSISTENT_DEVICE_ID_DEFAULT, + OP_SCHEDULE_EXACT_ALARM); if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) { - setUidMode(uid, OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED); + setUidMode( + uid, + PERSISTENT_DEVICE_ID_DEFAULT, + OP_SCHEDULE_EXACT_ALARM, + MODE_ALLOWED); } } // This appop is meant to be controlled at a uid level. So we leave package modes as @@ -641,7 +650,10 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface final int flags = permissionManager.getPermissionFlags(pkg, permissionName, UserHandle.of(userId)); if ((flags & PackageManager.FLAG_PERMISSION_USER_SET) == 0) { - setUidMode(uid, OP_USE_FULL_SCREEN_INTENT, + setUidMode( + uid, + PERSISTENT_DEVICE_ID_DEFAULT, + OP_USE_FULL_SCREEN_INTENT, AppOpsManager.opToDefaultMode(OP_USE_FULL_SCREEN_INTENT)); } } diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java index f6e6bc0be8fa..f056f6b10b0d 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java @@ -59,8 +59,9 @@ public interface AppOpsCheckingServiceInterface { * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid. * Returns an empty SparseIntArray if nothing is set. * @param uid for which we need the app-ops and their modes. + * @param persistentDeviceId device for which we need the app-ops and their modes */ - SparseIntArray getNonDefaultUidModes(int uid); + SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId); /** * Returns a copy of non-default app-ops with op as keys and their modes as values for a package @@ -75,20 +76,22 @@ public interface AppOpsCheckingServiceInterface { * Returns the app-op mode for a particular app-op of a uid. * Returns default op mode if the op mode for particular uid and op is not set. * @param uid user id for which we need the mode. + * @param persistentDeviceId device for which we need the mode * @param op app-op for which we need the mode. * @return mode of the app-op. */ - int getUidMode(int uid, int op); + int getUidMode(int uid, String persistentDeviceId, int op); /** * Set the app-op mode for a particular uid and op. * The mode is not set if the mode is the same as the default mode for the op. * @param uid user id for which we want to set the mode. + * @param persistentDeviceId device for which we want to set the mode. * @param op app-op for which we want to set the mode. * @param mode mode for the app-op. * @return true if op mode is changed. */ - boolean setUidMode(int uid, int op, @Mode int mode); + boolean setUidMode(int uid, String persistentDeviceId, int op, @Mode int mode); /** * Gets the app-op mode for a particular package. @@ -130,10 +133,11 @@ public interface AppOpsCheckingServiceInterface { /** * @param uid UID to query foreground ops for. + * @param persistentDeviceId device to query foreground ops for * @return SparseBooleanArray where the keys are the op codes for which their modes are * MODE_FOREGROUND for the passed UID. */ - SparseBooleanArray getForegroundOps(int uid); + SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId); /** * diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java index ccdf3a5baa7b..f6da166d9a34 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java @@ -60,9 +60,9 @@ public class AppOpsCheckingServiceLoggingDecorator implements AppOpsCheckingServ } @Override - public SparseIntArray getNonDefaultUidModes(int uid) { + public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) { Log.i(LOG_TAG, "getNonDefaultUidModes(uid = " + uid + ")"); - return mService.getNonDefaultUidModes(uid); + return mService.getNonDefaultUidModes(uid, persistentDeviceId); } @Override @@ -73,15 +73,15 @@ public class AppOpsCheckingServiceLoggingDecorator implements AppOpsCheckingServ } @Override - public int getUidMode(int uid, int op) { + public int getUidMode(int uid, String persistentDeviceId, int op) { Log.i(LOG_TAG, "getUidMode(uid = " + uid + ", op = " + op + ")"); - return mService.getUidMode(uid, op); + return mService.getUidMode(uid, persistentDeviceId, op); } @Override - public boolean setUidMode(int uid, int op, int mode) { + public boolean setUidMode(int uid, String persistentDeviceId, int op, int mode) { Log.i(LOG_TAG, "setUidMode(uid = " + uid + ", op = " + op + ", mode = " + mode + ")"); - return mService.setUidMode(uid, op, mode); + return mService.setUidMode(uid, persistentDeviceId, op, mode); } @Override @@ -117,9 +117,9 @@ public class AppOpsCheckingServiceLoggingDecorator implements AppOpsCheckingServ } @Override - public SparseBooleanArray getForegroundOps(int uid) { + public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) { Log.i(LOG_TAG, "getForegroundOps(uid = " + uid + ")"); - return mService.getForegroundOps(uid); + return mService.getForegroundOps(uid, persistentDeviceId); } @Override diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java index c3a02a84a277..55cf7ed80483 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java @@ -81,11 +81,11 @@ public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServ } @Override - public SparseIntArray getNonDefaultUidModes(int uid) { + public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getNonDefaultUidModes"); try { - return mService.getNonDefaultUidModes(uid); + return mService.getNonDefaultUidModes(uid, persistentDeviceId); } finally { Trace.traceEnd(TRACE_TAG); } @@ -103,20 +103,21 @@ public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServ } @Override - public int getUidMode(int uid, int op) { + public int getUidMode(int uid, String persistentDeviceId, int op) { Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getUidMode"); try { - return mService.getUidMode(uid, op); + return mService.getUidMode(uid, persistentDeviceId, op); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public boolean setUidMode(int uid, int op, @AppOpsManager.Mode int mode) { + public boolean setUidMode( + int uid, String persistentDeviceId, int op, @AppOpsManager.Mode int mode) { Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setUidMode"); try { - return mService.setUidMode(uid, op, mode); + return mService.setUidMode(uid, persistentDeviceId, op, mode); } finally { Trace.traceEnd(TRACE_TAG); } @@ -179,11 +180,11 @@ public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServ } @Override - public SparseBooleanArray getForegroundOps(int uid) { + public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getForegroundOps"); try { - return mService.getForegroundOps(uid); + return mService.getForegroundOps(uid, persistentDeviceId); } finally { Trace.traceEnd(TRACE_TAG); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 14aab13bf638..344673793700 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -63,6 +63,7 @@ import static android.app.AppOpsManager.opAllowSystemBypassRestriction; import static android.app.AppOpsManager.opRestrictsRead; import static android.app.AppOpsManager.opToName; import static android.app.AppOpsManager.opToPublicName; +import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; @@ -1349,7 +1350,10 @@ public class AppOpsService extends IAppOpsService.Stub { SparseBooleanArray foregroundOps = new SparseBooleanArray(); - SparseBooleanArray uidForegroundOps = mAppOpsCheckingService.getForegroundOps(uid); + // TODO(b/299330771): Check uidForegroundOps for all devices. + SparseBooleanArray uidForegroundOps = + mAppOpsCheckingService.getForegroundOps( + uid, PERSISTENT_DEVICE_ID_DEFAULT); for (int i = 0; i < uidForegroundOps.size(); i++) { foregroundOps.put(uidForegroundOps.keyAt(i), true); } @@ -1369,10 +1373,16 @@ public class AppOpsService extends IAppOpsService.Stub { continue; } final int code = foregroundOps.keyAt(fgi); - - if (mAppOpsCheckingService.getUidMode(uidState.uid, code) + // TODO(b/299330771): Notify op changes for all relevant devices. + if (mAppOpsCheckingService.getUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + code) != AppOpsManager.opToDefaultMode(code) - && mAppOpsCheckingService.getUidMode(uidState.uid, code) + && mAppOpsCheckingService.getUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + code) == AppOpsManager.MODE_FOREGROUND) { mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChangedForAllPkgsInUid, @@ -1489,7 +1499,11 @@ public class AppOpsService extends IAppOpsService.Stub { @Nullable private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState, @Nullable int[] ops) { - final SparseIntArray opModes = mAppOpsCheckingService.getNonDefaultUidModes(uidState.uid); + // TODO(b/299330771): Make this methods device-aware, currently it represents only the + // primary device. + final SparseIntArray opModes = + mAppOpsCheckingService.getNonDefaultUidModes( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT); if (opModes == null) { return null; } @@ -1844,16 +1858,22 @@ public class AppOpsService extends IAppOpsService.Stub { uidState = new UidState(uid); mUidStates.put(uid, uidState); } - if (mAppOpsCheckingService.getUidMode(uidState.uid, code) + // TODO(b/266164193): Ensure this behavior is device-aware after uid op mode for runtime + // permissions is deprecated. + if (mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code) != AppOpsManager.opToDefaultMode(code)) { - previousMode = mAppOpsCheckingService.getUidMode(uidState.uid, code); + previousMode = + mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code); } else { // doesn't look right but is legacy behavior. previousMode = MODE_DEFAULT; } mIgnoredCallback = permissionPolicyCallback; - if (!mAppOpsCheckingService.setUidMode(uidState.uid, code, mode)) { + if (!mAppOpsCheckingService.setUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code, mode)) { return; } if (mode != MODE_ERRORED && mode != previousMode) { @@ -2275,8 +2295,10 @@ public class AppOpsService extends IAppOpsService.Stub { boolean changed = false; for (int i = mUidStates.size() - 1; i >= 0; i--) { UidState uidState = mUidStates.valueAt(i); - - SparseIntArray opModes = mAppOpsCheckingService.getNonDefaultUidModes(uidState.uid); + // TODO(b/299330771): Check non default modes for all devices. + SparseIntArray opModes = + mAppOpsCheckingService.getNonDefaultUidModes( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT); if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) { final int uidOpCount = opModes.size(); for (int j = uidOpCount - 1; j >= 0; j--) { @@ -2285,7 +2307,12 @@ public class AppOpsService extends IAppOpsService.Stub { int previousMode = opModes.valueAt(j); int newMode = isUidOpGrantedByRole(uidState.uid, code) ? MODE_ALLOWED : AppOpsManager.opToDefaultMode(code); - mAppOpsCheckingService.setUidMode(uidState.uid, code, newMode); + // TODO(b/299330771): Set mode for all necessary devices. + mAppOpsCheckingService.setUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + code, + newMode); for (String packageName : getPackagesForUid(uidState.uid)) { callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, previousMode, mOpModeWatchers.get(code)); @@ -2601,10 +2628,14 @@ public class AppOpsService extends IAppOpsService.Stub { } code = AppOpsManager.opToSwitch(code); UidState uidState = getUidStateLocked(uid, false); + // TODO(b/299330771): Check mode for the relevant device. if (uidState != null - && mAppOpsCheckingService.getUidMode(uidState.uid, code) + && mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code) != AppOpsManager.opToDefaultMode(code)) { - final int rawMode = mAppOpsCheckingService.getUidMode(uidState.uid, code); + final int rawMode = + mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, code); return raw ? rawMode : uidState.evalMode(code, rawMode); } Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false); @@ -2851,13 +2882,19 @@ public class AppOpsService extends IAppOpsService.Stub { return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, packageName); } + // TODO(b/299330771): Check mode for the relevant device. // If there is a non-default per UID policy (we set UID op mode only if // non-default) it takes over, otherwise use the per package policy. - if (mAppOpsCheckingService.getUidMode(uidState.uid, switchCode) + if (mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { final int uidMode = uidState.evalMode( - code, mAppOpsCheckingService.getUidMode(uidState.uid, switchCode)); + code, + mAppOpsCheckingService.getUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + switchCode)); if (uidMode != AppOpsManager.MODE_ALLOWED) { if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " @@ -3396,13 +3433,19 @@ public class AppOpsService extends IAppOpsService.Stub { isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false); final int switchCode = AppOpsManager.opToSwitch(code); + // TODO(b/299330771): Check mode for the relevant device. // If there is a non-default per UID policy (we set UID op mode only if // non-default) it takes over, otherwise use the per package policy. - if (mAppOpsCheckingService.getUidMode(uidState.uid, switchCode) + if (mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { final int uidMode = uidState.evalMode( - code, mAppOpsCheckingService.getUidMode(uidState.uid, switchCode)); + code, + mAppOpsCheckingService.getUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + switchCode)); if (!shouldStartForMode(uidMode, startIfModeDefault)) { if (DEBUG) { Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code " @@ -3511,13 +3554,19 @@ public class AppOpsService extends IAppOpsService.Stub { isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false); final int switchCode = AppOpsManager.opToSwitch(code); + // TODO(b/299330771): Check mode for the relevant device. // If there is a non-default mode per UID policy (we set UID op mode only if // non-default) it takes over, otherwise use the per package policy. - if (mAppOpsCheckingService.getUidMode(uidState.uid, switchCode) + if (mAppOpsCheckingService.getUidMode( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT, switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { final int uidMode = uidState.evalMode( - code, mAppOpsCheckingService.getUidMode(uidState.uid, switchCode)); + code, + mAppOpsCheckingService.getUidMode( + uidState.uid, + PERSISTENT_DEVICE_ID_DEFAULT, + switchCode)); if (!shouldStartForMode(uidMode, startIfModeDefault)) { if (DEBUG) { Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code " @@ -5664,8 +5713,10 @@ public class AppOpsService extends IAppOpsService.Stub { } for (int i=0; i<mUidStates.size(); i++) { UidState uidState = mUidStates.valueAt(i); + // TODO(b/299330771): Get modes for all devices. final SparseIntArray opModes = - mAppOpsCheckingService.getNonDefaultUidModes(uidState.uid); + mAppOpsCheckingService.getNonDefaultUidModes( + uidState.uid, PERSISTENT_DEVICE_ID_DEFAULT); final ArrayMap<String, Ops> pkgOps = uidState.pkgOps; if (dumpWatchers || dumpHistory) { diff --git a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java index 98e6476e9707..c9fa9e600ecc 100644 --- a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java +++ b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java @@ -65,9 +65,9 @@ public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface } @Override - public SparseIntArray getNonDefaultUidModes(int uid) { - SparseIntArray oldVal = mOldImplementation.getNonDefaultUidModes(uid); - SparseIntArray newVal = mNewImplementation.getNonDefaultUidModes(uid); + public SparseIntArray getNonDefaultUidModes(int uid, String persistentDeviceId) { + SparseIntArray oldVal = mOldImplementation.getNonDefaultUidModes(uid, persistentDeviceId); + SparseIntArray newVal = mNewImplementation.getNonDefaultUidModes(uid, persistentDeviceId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("getNonDefaultUidModes"); @@ -89,9 +89,9 @@ public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface } @Override - public int getUidMode(int uid, int op) { - int oldVal = mOldImplementation.getUidMode(uid, op); - int newVal = mNewImplementation.getUidMode(uid, op); + public int getUidMode(int uid, String persistentDeviceId, int op) { + int oldVal = mOldImplementation.getUidMode(uid, persistentDeviceId, op); + int newVal = mNewImplementation.getUidMode(uid, persistentDeviceId, op); if (oldVal != newVal) { signalImplDifference("getUidMode"); @@ -101,9 +101,9 @@ public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface } @Override - public boolean setUidMode(int uid, int op, int mode) { - boolean oldVal = mOldImplementation.setUidMode(uid, op, mode); - boolean newVal = mNewImplementation.setUidMode(uid, op, mode); + public boolean setUidMode(int uid, String persistentDeviceId, int op, int mode) { + boolean oldVal = mOldImplementation.setUidMode(uid, persistentDeviceId, op, mode); + boolean newVal = mNewImplementation.setUidMode(uid, persistentDeviceId, op, mode); if (oldVal != newVal) { signalImplDifference("setUidMode"); @@ -155,9 +155,9 @@ public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface } @Override - public SparseBooleanArray getForegroundOps(int uid) { - SparseBooleanArray oldVal = mOldImplementation.getForegroundOps(uid); - SparseBooleanArray newVal = mNewImplementation.getForegroundOps(uid); + public SparseBooleanArray getForegroundOps(int uid, String persistentDeviceId) { + SparseBooleanArray oldVal = mOldImplementation.getForegroundOps(uid, persistentDeviceId); + SparseBooleanArray newVal = mNewImplementation.getForegroundOps(uid, persistentDeviceId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("getForegroundOps"); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index df106a794041..d4b72c1770f7 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -18,6 +18,8 @@ package com.android.server.audio; import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; +import static android.media.audio.Flags.autoPublicVolumeApiHardening; +import static android.media.audio.Flags.focusFreezeTestApi; import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; @@ -107,6 +109,7 @@ import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.AudioSystem; +import android.media.AudioTrack; import android.media.BluetoothProfileConnectionInfo; import android.media.IAudioDeviceVolumeDispatcher; import android.media.IAudioFocusDispatcher; @@ -133,7 +136,9 @@ import android.media.IStrategyNonDefaultDevicesDispatcher; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.IStreamAliasingDispatcher; import android.media.IVolumeController; -import android.media.LoudnessCodecFormat; +import android.media.LoudnessCodecConfigurator; +import android.media.LoudnessCodecInfo; +import android.media.MediaCodec; import android.media.MediaMetrics; import android.media.MediaRecorder.AudioSource; import android.media.PlayerBase; @@ -154,6 +159,7 @@ 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; @@ -311,6 +317,9 @@ 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; @@ -347,7 +356,7 @@ public class AudioService extends IAudioService.Stub } /*package*/ boolean isPlatformAutomotive() { - return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + return mPlatformType == AudioSystem.PLATFORM_AUTOMOTIVE; } /** The controller for the volume UI. */ @@ -941,6 +950,10 @@ public class AudioService extends IAudioService.Stub private final SoundDoseHelper mSoundDoseHelper; + private final LoudnessCodecHelper mLoudnessCodecHelper; + + private final HardeningEnforcer mHardeningEnforcer; + private final Object mSupportedSystemUsagesLock = new Object(); @GuardedBy("mSupportedSystemUsagesLock") private @AttributeSystemUsage int[] mSupportedSystemUsages = @@ -1275,6 +1288,8 @@ public class AudioService extends IAudioService.Stub readPersistedSettings(); readUserRestrictions(); + mLoudnessCodecHelper = new LoudnessCodecHelper(this); + mPlaybackMonitor = new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM], device -> onMuteAwaitConnectionTimeout(device)); @@ -1315,6 +1330,8 @@ public class AudioService extends IAudioService.Stub mDisplayManager = context.getSystemService(DisplayManager.class); mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler); + + mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive()); } private void initVolumeStreamStates() { @@ -1386,7 +1403,6 @@ public class AudioService extends IAudioService.Stub // check on volume initialization checkVolumeRangeInitialization("AudioService()"); - } private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener = @@ -1399,6 +1415,14 @@ 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 @@ -3427,6 +3451,10 @@ 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); @@ -3980,7 +4008,8 @@ public class AudioService extends IAudioService.Stub } setStreamVolume(groupedStream, index, flags, /*device*/ null, callingPackage, callingPackage, - attributionTag, Binder.getCallingUid(), true /*hasModifyAudioSettings*/); + attributionTag, Binder.getCallingUid(), true /*hasModifyAudioSettings*/, + true /*canChangeMuteAndUpdateController*/); } } @@ -4099,7 +4128,9 @@ public class AudioService extends IAudioService.Stub setStreamVolumeWithAttributionInt(vi.getStreamType(), mStreamStates[vi.getStreamType()].getMinIndex(), /*flags*/ 0, - ada, callingPackage, null); + ada, callingPackage, null, + //TODO handle unmuting of current audio device + false /*canChangeMuteAndUpdateController*/); return; } @@ -4125,7 +4156,8 @@ public class AudioService extends IAudioService.Stub } } setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0, - ada, callingPackage, null); + ada, callingPackage, null, + false /*canChangeMuteAndUpdateController*/); } /** Retain API for unsupported app usage */ @@ -4203,8 +4235,12 @@ 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); + callingPackage, attributionTag, true /*canChangeMuteAndUpdateController*/); } /** @@ -4217,10 +4253,18 @@ public class AudioService extends IAudioService.Stub * for which volume is being changed * @param callingPackage client side-provided package name of caller, not to be trusted * @param attributionTag client side-provided attribution name, not to be trusted + * @param canChangeMuteAndUpdateController true if the calling method is a path where + * the volume change is allowed to update the mute state as well as update + * the volume controller (the UI). This is intended to be true for a call coming + * from AudioManager.setStreamVolume (which is here + * {@link #setStreamVolumeForUid(int, int, int, String, int, int, UserHandle, int)}, + * and false when coming from AudioDeviceVolumeManager.setDeviceVolume (which is here + * {@link #setDeviceVolume(VolumeInfo, AudioDeviceAttributes, String)} */ protected void setStreamVolumeWithAttributionInt(int streamType, int index, int flags, - @Nullable AudioDeviceAttributes device, - String callingPackage, String attributionTag) { + @Nullable AudioDeviceAttributes ada, + String callingPackage, String attributionTag, + boolean canChangeMuteAndUpdateController) { if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) { Log.w(TAG, "Trying to call setStreamVolume() for a11y without" + " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage); @@ -4243,15 +4287,18 @@ public class AudioService extends IAudioService.Stub return; } - if (device == null) { + if (ada == null) { // call was already logged in setDeviceVolume() + final int deviceType = getDeviceForStream(streamType); sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, - index/*val1*/, flags/*val2*/, getStreamVolume(streamType) /*val3*/, + index/*val1*/, flags/*val2*/, getStreamVolume(streamType, deviceType) /*val3*/, callingPackage)); + ada = new AudioDeviceAttributes(deviceType /*nativeType*/, "" /*address*/); } - setStreamVolume(streamType, index, flags, device, + setStreamVolume(streamType, index, flags, ada, callingPackage, callingPackage, attributionTag, - Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission()); + Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission(), + canChangeMuteAndUpdateController); } @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_ULTRASOUND) @@ -4351,6 +4398,8 @@ public class AudioService extends IAudioService.Stub mSoundDoseHelper.scheduleMusicActiveCheck(); } + mLoudnessCodecHelper.updateCodecParameters(configs); + // Update playback active state for all apps in audio mode stack. // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE // and request an audio mode update immediately. Upon any other change, queue the message @@ -4433,6 +4482,18 @@ public class AudioService extends IAudioService.Stub null /* playbackConfigs */, configs /* recordConfigs */); } + private void dumpFlags(PrintWriter pw) { + pw.println("\nFun with Flags: "); + pw.println("\tandroid.media.audio.Flags.autoPublicVolumeApiHardening:" + + autoPublicVolumeApiHardening()); + pw.println("\tandroid.media.audio.Flags.focusFreezeTestApi:" + + focusFreezeTestApi()); + pw.println("\tcom.android.media.audio.Flags.bluetoothMacAddressAnonymization:" + + bluetoothMacAddressAnonymization()); + pw.println("\tcom.android.media.audio.Flags.disablePrescaleAbsoluteVolume:" + + disablePrescaleAbsoluteVolume()); + } + private void dumpAudioMode(PrintWriter pw) { pw.println("\nAudio mode: "); pw.println("- Requested mode = " + AudioSystem.modeToString(getMode())); @@ -4539,9 +4600,11 @@ public class AudioService extends IAudioService.Stub } private void setStreamVolume(int streamType, int index, int flags, - @Nullable AudioDeviceAttributes ada, + @NonNull AudioDeviceAttributes ada, String callingPackage, String caller, String attributionTag, int uid, - boolean hasModifyAudioSettings) { + boolean hasModifyAudioSettings, + boolean canChangeMuteAndUpdateController) { + if (DEBUG_VOL) { Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index + ", dev=" + ada @@ -4555,9 +4618,7 @@ public class AudioService extends IAudioService.Stub int streamTypeAlias = mStreamVolumeAlias[streamType]; VolumeStreamState streamState = mStreamStates[streamTypeAlias]; - final int device = (ada == null) - ? getDeviceForStream(streamType) - : ada.getInternalType(); + final int device = ada.getInternalType(); int oldIndex; // skip a2dp absolute volume control request when the device @@ -4644,7 +4705,7 @@ public class AudioService extends IAudioService.Stub onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings, // ada is non-null when called from setDeviceVolume, // which shouldn't update the mute state - ada == null /*canChangeMute*/); + canChangeMuteAndUpdateController /*canChangeMute*/); index = mStreamStates[streamType].getIndex(device); } @@ -4654,7 +4715,7 @@ public class AudioService extends IAudioService.Stub maybeSendSystemAudioStatusCommand(false); } } - if (ada == null) { + if (canChangeMuteAndUpdateController) { // only non-null when coming here from setDeviceVolume // TODO change test to check early if device is current device or not sendVolumeUpdate(streamType, oldIndex, index, flags, device); @@ -5057,6 +5118,7 @@ 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, @@ -5067,6 +5129,10 @@ public class AudioService extends IAudioService.Stub public int getStreamVolume(int streamType) { ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); + return getStreamVolume(streamType, device); + } + + private int getStreamVolume(int streamType, int device) { synchronized (VolumeStreamState.class) { int index = mStreamStates[streamType].getIndex(device); @@ -5422,6 +5488,10 @@ 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"); @@ -6174,6 +6244,35 @@ 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, @@ -6185,7 +6284,8 @@ public class AudioService extends IAudioService.Stub setStreamVolume(streamType, index, flags, /*device*/ null, packageName, packageName, null, uid, - hasAudioSettingsPermission(uid, pid)); + hasAudioSettingsPermission(uid, pid), + true /*canChangeMuteAndUpdateController*/); } //========================================================================================== @@ -10542,44 +10642,43 @@ public class AudioService extends IAudioService.Stub @Override public void registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher) { - // TODO: implement + mLoudnessCodecHelper.registerLoudnessCodecUpdatesDispatcher(dispatcher); } @Override public void unregisterLoudnessCodecUpdatesDispatcher( ILoudnessCodecUpdatesDispatcher dispatcher) { - // TODO: implement + mLoudnessCodecHelper.unregisterLoudnessCodecUpdatesDispatcher(dispatcher); } + /** @see LoudnessCodecConfigurator#setAudioTrack(AudioTrack) */ @Override - public void startLoudnessCodecUpdates(int piid) { - // TODO: implement + public void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) { + mLoudnessCodecHelper.startLoudnessCodecUpdates(piid, codecInfoList); } + /** @see LoudnessCodecConfigurator#setAudioTrack(AudioTrack) */ @Override public void stopLoudnessCodecUpdates(int piid) { - // TODO: implement + mLoudnessCodecHelper.stopLoudnessCodecUpdates(piid); } + /** @see LoudnessCodecConfigurator#addMediaCodec(MediaCodec) */ @Override - public void addLoudnesssCodecFormat(int piid, LoudnessCodecFormat format) { - // TODO: implement + public void addLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) { + mLoudnessCodecHelper.addLoudnessCodecInfo(piid, codecInfo); } + /** @see LoudnessCodecConfigurator#removeMediaCodec(MediaCodec) */ @Override - public void addLoudnesssCodecFormatList(int piid, List<LoudnessCodecFormat> format) { - // TODO: implement + public void removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) { + mLoudnessCodecHelper.removeLoudnessCodecInfo(piid, codecInfo); } + /** @see LoudnessCodecConfigurator#getLoudnessCodecParams(AudioTrack, MediaCodec) */ @Override - public void removeLoudnessCodecFormat(int piid, LoudnessCodecFormat format) { - // TODO: implement - } - - @Override - public PersistableBundle getLoudnessParams(int piid, LoudnessCodecFormat format) { - // TODO: implement - return null; + public PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) { + return mLoudnessCodecHelper.getLoudnessParams(piid, codecInfo); } //========================================================================================== @@ -11377,6 +11476,7 @@ public class AudioService extends IAudioService.Stub } else { pw.println("\nMessage handler is null"); } + dumpFlags(pw); mMediaFocusControl.dump(pw); dumpStreamStates(pw); dumpVolumeGroups(pw); diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java new file mode 100644 index 000000000000..4ceb83b2e1c9 --- /dev/null +++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java @@ -0,0 +1,109 @@ +/* + * 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/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java new file mode 100644 index 000000000000..3c67e9dd116b --- /dev/null +++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java @@ -0,0 +1,513 @@ +/* + * 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.server.audio; + +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH; +import static android.media.AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.media.AudioDeviceInfo; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioSystem; +import android.media.ILoudnessCodecUpdatesDispatcher; +import android.media.LoudnessCodecInfo; +import android.media.permission.ClearCallingIdentityContext; +import android.media.permission.SafeCloseable; +import android.os.Binder; +import android.os.PersistableBundle; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Log; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import com.android.internal.annotations.GuardedBy; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * Class to handle the updates in loudness parameters and responsible to generate parameters that + * can be set directly on a MediaCodec. + */ +public class LoudnessCodecHelper { + private static final String TAG = "AS.LoudnessCodecHelper"; + + private static final boolean DEBUG = false; + + /** + * Property containing a string to set for a custom built in speaker SPL range as defined by + * CTA2075. The options that can be set are: + * - "small": for max SPL with test signal < 75 dB, + * - "medium": for max SPL with test signal between 70 and 90 dB, + * - "large": for max SPL with test signal > 85 dB. + */ + private static final String SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE = + "audio.loudness.builtin-speaker-spl-range-size"; + + private static final int SPL_RANGE_UNKNOWN = 0; + private static final int SPL_RANGE_SMALL = 1; + private static final int SPL_RANGE_MEDIUM = 2; + private static final int SPL_RANGE_LARGE = 3; + + /** The possible transducer SPL ranges as defined in CTA2075 */ + @IntDef({ + SPL_RANGE_UNKNOWN, + SPL_RANGE_SMALL, + SPL_RANGE_MEDIUM, + SPL_RANGE_LARGE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceSplRange {} + + private static final class LoudnessRemoteCallbackList extends + RemoteCallbackList<ILoudnessCodecUpdatesDispatcher> { + private final LoudnessCodecHelper mLoudnessCodecHelper; + LoudnessRemoteCallbackList(LoudnessCodecHelper loudnessCodecHelper) { + mLoudnessCodecHelper = loudnessCodecHelper; + } + + @Override + public void onCallbackDied(ILoudnessCodecUpdatesDispatcher callback, Object cookie) { + Integer pid = null; + if (cookie instanceof Integer) { + pid = (Integer) cookie; + } + if (pid != null) { + mLoudnessCodecHelper.removePid(pid); + } + super.onCallbackDied(callback, cookie); + } + } + + private final LoudnessRemoteCallbackList mLoudnessUpdateDispatchers = + new LoudnessRemoteCallbackList(this); + + private final Object mLock = new Object(); + + /** Contains for each started piid the set corresponding to unique registered audio codecs. */ + @GuardedBy("mLock") + private final SparseArray<Set<LoudnessCodecInfo>> mStartedPiids = new SparseArray<>(); + + /** Contains the current device id assignment for each piid. */ + @GuardedBy("mLock") + private final SparseIntArray mPiidToDeviceIdCache = new SparseIntArray(); + + /** Maps each piid to the owner process of the player. */ + @GuardedBy("mLock") + private final SparseIntArray mPiidToPidCache = new SparseIntArray(); + + private final AudioService mAudioService; + + /** Contains the properties necessary to compute the codec loudness related parameters. */ + private static final class LoudnessCodecInputProperties { + private final int mMetadataType; + + private final boolean mIsDownmixing; + + @DeviceSplRange + private final int mDeviceSplRange; + + static final class Builder { + private int mMetadataType; + + private boolean mIsDownmixing; + + @DeviceSplRange + private int mDeviceSplRange; + + Builder setMetadataType(int metadataType) { + mMetadataType = metadataType; + return this; + } + Builder setIsDownmixing(boolean isDownmixing) { + mIsDownmixing = isDownmixing; + return this; + } + Builder setDeviceSplRange(@DeviceSplRange int deviceSplRange) { + mDeviceSplRange = deviceSplRange; + return this; + } + + LoudnessCodecInputProperties build() { + return new LoudnessCodecInputProperties(mMetadataType, + mIsDownmixing, mDeviceSplRange); + } + } + + private LoudnessCodecInputProperties(int metadataType, + boolean isDownmixing, + @DeviceSplRange int deviceSplRange) { + mMetadataType = metadataType; + mIsDownmixing = isDownmixing; + mDeviceSplRange = deviceSplRange; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + // type check and cast + if (getClass() != obj.getClass()) { + return false; + } + final LoudnessCodecInputProperties lcip = (LoudnessCodecInputProperties) obj; + return mMetadataType == lcip.mMetadataType + && mIsDownmixing == lcip.mIsDownmixing + && mDeviceSplRange == lcip.mDeviceSplRange; + } + + @Override + public int hashCode() { + return Objects.hash(mMetadataType, mIsDownmixing, mDeviceSplRange); + } + + @Override + public String toString() { + return "Loudness properties:" + + " device SPL range: " + splRangeToString(mDeviceSplRange) + + " down-mixing: " + mIsDownmixing + + " metadata type: " + mMetadataType; + } + + PersistableBundle createLoudnessParameters() { + // TODO: create bundle with new parameters + return new PersistableBundle(); + } + + } + + @GuardedBy("mLock") + private final HashMap<LoudnessCodecInputProperties, PersistableBundle> mCachedProperties = + new HashMap<>(); + + LoudnessCodecHelper(@NonNull AudioService audioService) { + mAudioService = Objects.requireNonNull(audioService); + } + + void registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher) { + mLoudnessUpdateDispatchers.register(dispatcher, Binder.getCallingPid()); + } + + void unregisterLoudnessCodecUpdatesDispatcher( + ILoudnessCodecUpdatesDispatcher dispatcher) { + mLoudnessUpdateDispatchers.unregister(dispatcher); + } + + void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) { + if (DEBUG) { + Log.d(TAG, "startLoudnessCodecUpdates: piid " + piid + " codecInfos " + codecInfoList); + } + Set<LoudnessCodecInfo> infoSet; + synchronized (mLock) { + if (mStartedPiids.contains(piid)) { + Log.w(TAG, "Already started loudness updates for piid " + piid); + return; + } + infoSet = new HashSet<>(codecInfoList); + mStartedPiids.put(piid, infoSet); + + mPiidToPidCache.put(piid, Binder.getCallingPid()); + } + + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + mAudioService.getActivePlaybackConfigurations().stream().filter( + conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent( + apc -> updateCodecParametersForConfiguration(apc, infoSet)); + } + } + + void stopLoudnessCodecUpdates(int piid) { + if (DEBUG) { + Log.d(TAG, "stopLoudnessCodecUpdates: piid " + piid); + } + synchronized (mLock) { + if (!mStartedPiids.contains(piid)) { + Log.w(TAG, "Loudness updates are already stopped for piid " + piid); + return; + } + mStartedPiids.remove(piid); + mPiidToDeviceIdCache.delete(piid); + mPiidToPidCache.delete(piid); + } + } + + void addLoudnessCodecInfo(int piid, LoudnessCodecInfo info) { + if (DEBUG) { + Log.d(TAG, "addLoudnessCodecInfo: piid " + piid + " info " + info); + } + + Set<LoudnessCodecInfo> infoSet; + synchronized (mLock) { + if (!mStartedPiids.contains(piid)) { + Log.w(TAG, "Cannot add new loudness info for stopped piid " + piid); + return; + } + + infoSet = mStartedPiids.get(piid); + infoSet.add(info); + } + + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + mAudioService.getActivePlaybackConfigurations().stream().filter( + conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent( + apc -> updateCodecParametersForConfiguration(apc, Set.of(info))); + } + } + + void removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) { + if (DEBUG) { + Log.d(TAG, "removeLoudnessCodecInfo: piid " + piid + " info " + codecInfo); + } + synchronized (mLock) { + if (!mStartedPiids.contains(piid)) { + Log.w(TAG, "Cannot remove loudness info for stopped piid " + piid); + return; + } + final Set<LoudnessCodecInfo> infoSet = mStartedPiids.get(piid); + infoSet.remove(codecInfo); + } + } + + void removePid(int pid) { + if (DEBUG) { + Log.d(TAG, "Removing pid " + pid + " from receiving updates"); + } + synchronized (mLock) { + for (int i = 0; i < mPiidToPidCache.size(); ++i) { + int piid = mPiidToPidCache.keyAt(i); + if (mPiidToPidCache.get(piid) == pid) { + if (DEBUG) { + Log.d(TAG, "Removing piid " + piid); + } + mStartedPiids.delete(piid); + mPiidToDeviceIdCache.delete(piid); + } + } + } + } + + PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) { + if (DEBUG) { + Log.d(TAG, "getLoudnessParams: piid " + piid + " codecInfo " + codecInfo); + } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + final List<AudioPlaybackConfiguration> configs = + mAudioService.getActivePlaybackConfigurations(); + + for (final AudioPlaybackConfiguration apc : configs) { + if (apc.getPlayerInterfaceId() == piid) { + final AudioDeviceInfo info = apc.getAudioDeviceInfo(); + if (info == null) { + Log.i(TAG, "Player with piid " + piid + " is not assigned any device"); + break; + } + synchronized (mLock) { + return getCodecBundle_l(info, codecInfo); + } + } + } + } + + // return empty Bundle + return new PersistableBundle(); + } + + /** Method to be called whenever there is a changed in the active playback configurations. */ + void updateCodecParameters(List<AudioPlaybackConfiguration> configs) { + if (DEBUG) { + Log.d(TAG, "updateCodecParameters: configs " + configs); + } + + List<AudioPlaybackConfiguration> updateApcList = new ArrayList<>(); + synchronized (mLock) { + for (final AudioPlaybackConfiguration apc : configs) { + int piid = apc.getPlayerInterfaceId(); + int cachedDeviceId = mPiidToDeviceIdCache.get(piid, PLAYER_DEVICEID_INVALID); + AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo(); + if (deviceInfo == null) { + if (DEBUG) { + Log.d(TAG, "No device info for piid: " + piid); + } + if (cachedDeviceId != PLAYER_DEVICEID_INVALID) { + mPiidToDeviceIdCache.delete(piid); + if (DEBUG) { + Log.d(TAG, "Remove cached device id for piid: " + piid); + } + } + continue; + } + if (cachedDeviceId == deviceInfo.getId()) { + // deviceId did not change + if (DEBUG) { + Log.d(TAG, "DeviceId " + cachedDeviceId + " for piid: " + piid + + " did not change"); + } + continue; + } + mPiidToDeviceIdCache.put(piid, deviceInfo.getId()); + if (mStartedPiids.contains(piid)) { + updateApcList.add(apc); + } + } + } + + updateApcList.forEach(apc -> updateCodecParametersForConfiguration(apc, null)); + } + + /** Updates and dispatches the new loudness parameters for the {@code codecInfos} set. + * + * @param apc the player configuration for which the loudness parameters are updated. + * @param codecInfos the codec info for which the parameters are updated. If {@code null}, + * send updates for all the started codecs assigned to {@code apc} + */ + private void updateCodecParametersForConfiguration(AudioPlaybackConfiguration apc, + Set<LoudnessCodecInfo> codecInfos) { + if (DEBUG) { + Log.d(TAG, "updateCodecParametersForConfiguration apc:" + apc + " codecInfos: " + + codecInfos); + } + final PersistableBundle allBundles = new PersistableBundle(); + final int piid = apc.getPlayerInterfaceId(); + synchronized (mLock) { + if (codecInfos == null) { + codecInfos = mStartedPiids.get(piid); + } + + final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo(); + if (codecInfos != null && deviceInfo != null) { + for (LoudnessCodecInfo info : codecInfos) { + allBundles.putPersistableBundle(Integer.toString(info.mediaCodecHashCode), + getCodecBundle_l(deviceInfo, info)); + } + } + } + + if (!allBundles.isDefinitelyEmpty()) { + if (DEBUG) { + Log.d(TAG, "Dispatching for piid: " + piid + " bundle: " + allBundles); + } + dispatchNewLoudnessParameters(piid, allBundles); + } + } + + private void dispatchNewLoudnessParameters(int piid, PersistableBundle bundle) { + if (DEBUG) { + Log.d(TAG, "dispatchNewLoudnessParameters: piid " + piid); + } + final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast(); + for (int i = 0; i < nbDispatchers; ++i) { + try { + mLoudnessUpdateDispatchers.getBroadcastItem(i) + .dispatchLoudnessCodecParameterChange(piid, bundle); + } catch (RemoteException e) { + Log.e(TAG, "Error dispatching for piid: " + piid + " bundle: " + bundle , e); + } + } + mLoudnessUpdateDispatchers.finishBroadcast(); + } + + @GuardedBy("mLock") + private PersistableBundle getCodecBundle_l(AudioDeviceInfo deviceInfo, + LoudnessCodecInfo codecInfo) { + LoudnessCodecInputProperties.Builder builder = new LoudnessCodecInputProperties.Builder(); + LoudnessCodecInputProperties prop = builder.setDeviceSplRange(getDeviceSplRange(deviceInfo)) + .setIsDownmixing(codecInfo.isDownmixing) + .setMetadataType(codecInfo.metadataType) + .build(); + + if (mCachedProperties.containsKey(prop)) { + return mCachedProperties.get(prop); + } + final PersistableBundle codecBundle = prop.createLoudnessParameters(); + mCachedProperties.put(prop, codecBundle); + return codecBundle; + } + + @DeviceSplRange + private int getDeviceSplRange(AudioDeviceInfo deviceInfo) { + final int internalDeviceType = deviceInfo.getInternalType(); + if (internalDeviceType == AudioSystem.DEVICE_OUT_SPEAKER) { + final String splRange = SystemProperties.get( + SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE, "unknown"); + if (!splRange.equals("unknown")) { + return stringToSplRange(splRange); + } + + @DeviceSplRange int result = SPL_RANGE_SMALL; // default for phone/tablet/watch + if (mAudioService.isPlatformAutomotive() || mAudioService.isPlatformTelevision()) { + result = SPL_RANGE_MEDIUM; + } + + return result; + } else if (internalDeviceType == AudioSystem.DEVICE_OUT_USB_HEADSET + || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET + || (AudioSystem.isBluetoothDevice(internalDeviceType) + && mAudioService.getBluetoothAudioDeviceCategory(deviceInfo.getAddress(), + AudioSystem.isBluetoothLeDevice(internalDeviceType)) + == AUDIO_DEVICE_CATEGORY_HEADPHONES)) { + return SPL_RANGE_LARGE; + } else if (AudioSystem.isBluetoothDevice(internalDeviceType)) { + final int audioDeviceType = mAudioService.getBluetoothAudioDeviceCategory( + deviceInfo.getAddress(), AudioSystem.isBluetoothLeDevice(internalDeviceType)); + if (audioDeviceType == AUDIO_DEVICE_CATEGORY_CARKIT) { + return SPL_RANGE_MEDIUM; + } else if (audioDeviceType == AUDIO_DEVICE_CATEGORY_WATCH) { + return SPL_RANGE_SMALL; + } else if (audioDeviceType == AUDIO_DEVICE_CATEGORY_HEARING_AID) { + return SPL_RANGE_SMALL; + } + } + + return SPL_RANGE_UNKNOWN; + } + + private static String splRangeToString(@DeviceSplRange int splRange) { + switch (splRange) { + case SPL_RANGE_LARGE: return "large"; + case SPL_RANGE_MEDIUM: return "medium"; + case SPL_RANGE_SMALL: return "small"; + default: return "unknown"; + } + } + + @DeviceSplRange + private static int stringToSplRange(String splRange) { + switch (splRange) { + case "large": return SPL_RANGE_LARGE; + case "medium": return SPL_RANGE_MEDIUM; + case "small": return SPL_RANGE_SMALL; + default: return SPL_RANGE_UNKNOWN; + } + } +} diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 793752f3a1be..c72632fb367d 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -1297,7 +1297,8 @@ public class SoundDoseHelper { } final int index = safeMediaVolumeIndex(nativeDeviceType); mAudioService.setStreamVolumeWithAttributionInt(STREAM_MUSIC, index / 10, /*flags*/ 0, ada, - mContext.getOpPackageName(), /*attributionTag=*/null); + mContext.getOpPackageName(), /*attributionTag=*/null, + true /*canChangeMuteAndUpdateController*/); } // StreamVolumeCommand contains the information needed to defer the process of diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java index af33de0426b1..50ab3f8b8b6c 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java @@ -63,13 +63,27 @@ public interface DeviceStateProvider { */ int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED = 5; + /** + * Indicating that the supported device states have changed because an external display was + * added. + */ + int SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED = 6; + + /** + * Indicating that the supported device states have changed because an external display was + * removed. + */ + int SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED = 7; + @IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = { SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT, SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED, SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL, SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL, SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED, - SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED + SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED, + SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED, + SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED }) @Retention(RetentionPolicy.SOURCE) @interface SupportedStatesUpdatedReason {} diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index ba321ae5d807..db636d619bd3 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -17,6 +17,8 @@ package com.android.server.display; import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE; +import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS; +import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; import android.annotation.NonNull; import android.annotation.Nullable; @@ -33,6 +35,7 @@ import android.view.SurfaceControl; import com.android.server.display.layout.Layout; import com.android.server.display.mode.DisplayModeDirector; +import com.android.server.wm.utils.DisplayInfoOverrides; import com.android.server.wm.utils.InsetUtils; import java.io.PrintWriter; @@ -252,24 +255,8 @@ final class LogicalDisplay { public DisplayInfo getDisplayInfoLocked() { if (mInfo.get() == null) { DisplayInfo info = new DisplayInfo(); - info.copyFrom(mBaseDisplayInfo); - if (mOverrideDisplayInfo != null) { - info.appWidth = mOverrideDisplayInfo.appWidth; - info.appHeight = mOverrideDisplayInfo.appHeight; - info.smallestNominalAppWidth = mOverrideDisplayInfo.smallestNominalAppWidth; - info.smallestNominalAppHeight = mOverrideDisplayInfo.smallestNominalAppHeight; - info.largestNominalAppWidth = mOverrideDisplayInfo.largestNominalAppWidth; - info.largestNominalAppHeight = mOverrideDisplayInfo.largestNominalAppHeight; - info.logicalWidth = mOverrideDisplayInfo.logicalWidth; - info.logicalHeight = mOverrideDisplayInfo.logicalHeight; - info.physicalXDpi = mOverrideDisplayInfo.physicalXDpi; - info.physicalYDpi = mOverrideDisplayInfo.physicalYDpi; - info.rotation = mOverrideDisplayInfo.rotation; - info.displayCutout = mOverrideDisplayInfo.displayCutout; - info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi; - info.roundedCorners = mOverrideDisplayInfo.roundedCorners; - info.displayShape = mOverrideDisplayInfo.displayShape; - } + copyDisplayInfoFields(info, mBaseDisplayInfo, mOverrideDisplayInfo, + WM_OVERRIDE_FIELDS); mInfo.set(info); } return mInfo.get(); diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index aa8061222444..5cfbf26338e3 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -86,6 +86,10 @@ public class DisplayManagerFlags { Flags.FLAG_BRIGHTNESS_INT_RANGE_USER_PERCEPTION, Flags::brightnessIntRangeUserPerception); + private final FlagState mVsyncProximityVote = new FlagState( + Flags.FLAG_ENABLE_EXTERNAL_VSYNC_PROXIMITY_VOTE, + Flags::enableExternalVsyncProximityVote); + /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { return mConnectedDisplayManagementFlagState.isEnabled(); @@ -170,6 +174,10 @@ public class DisplayManagerFlags { return mBrightnessIntRangeUserPerceptionFlagState.isEnabled(); } + public boolean isExternalVsyncProximityVoteEnabled() { + return mVsyncProximityVote.isEnabled(); + } + /** * dumps all flagstates * @param pw printWriter @@ -188,6 +196,7 @@ public class DisplayManagerFlags { pw.println(" " + mPowerThrottlingClamperFlagState); pw.println(" " + mSmallAreaDetectionFlagState); pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState); + pw.println(" " + mVsyncProximityVote); } private static class FlagState { diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index e28b415e6488..d95bdae7514d 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -112,3 +112,11 @@ flag { bug: "183655602" is_fixed_read_only: true } + +flag { + name: "enable_external_vsync_proximity_vote" + namespace: "display_manager" + description: "Feature flag for external vsync proximity vote" + bug: "284866750" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java new file mode 100644 index 000000000000..c04df64fc15a --- /dev/null +++ b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.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.server.display.mode; + +import java.util.Objects; + +class BaseModeRefreshRateVote implements Vote { + + /** + * The preferred refresh rate selected by the app. It is used to validate that the summary + * refresh rate ranges include this value, and are not restricted by a lower priority vote. + */ + final float mAppRequestBaseModeRefreshRate; + + BaseModeRefreshRateVote(float baseModeRefreshRate) { + mAppRequestBaseModeRefreshRate = baseModeRefreshRate; + } + + @Override + public void updateSummary(DisplayModeDirector.VoteSummary summary) { + if (summary.appRequestBaseModeRefreshRate == 0f + && mAppRequestBaseModeRefreshRate > 0f) { + summary.appRequestBaseModeRefreshRate = mAppRequestBaseModeRefreshRate; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BaseModeRefreshRateVote that)) return false; + return Float.compare(that.mAppRequestBaseModeRefreshRate, + mAppRequestBaseModeRefreshRate) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(mAppRequestBaseModeRefreshRate); + } + + @Override + public String toString() { + return "BaseModeRefreshRateVote{ mAppRequestBaseModeRefreshRate=" + + mAppRequestBaseModeRefreshRate + " }"; + } +} diff --git a/services/core/java/com/android/server/display/mode/CombinedVote.java b/services/core/java/com/android/server/display/mode/CombinedVote.java new file mode 100644 index 000000000000..f24fe3a7eb04 --- /dev/null +++ b/services/core/java/com/android/server/display/mode/CombinedVote.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.display.mode; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +class CombinedVote implements Vote { + final List<Vote> mVotes; + + CombinedVote(List<Vote> votes) { + mVotes = Collections.unmodifiableList(votes); + } + + @Override + public void updateSummary(DisplayModeDirector.VoteSummary summary) { + mVotes.forEach(vote -> vote.updateSummary(summary)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CombinedVote that)) return false; + return Objects.equals(mVotes, that.mVotes); + } + + @Override + public int hashCode() { + return Objects.hash(mVotes); + } + + @Override + public String toString() { + return "CombinedVote{ mVotes=" + mVotes + " }"; + } +} diff --git a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java new file mode 100644 index 000000000000..2fc5590a4b94 --- /dev/null +++ b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.mode; + +import java.util.Objects; + +class DisableRefreshRateSwitchingVote implements Vote { + + /** + * Whether refresh rate switching should be disabled (i.e. the refresh rate range is + * a single value). + */ + final boolean mDisableRefreshRateSwitching; + + DisableRefreshRateSwitchingVote(boolean disableRefreshRateSwitching) { + mDisableRefreshRateSwitching = disableRefreshRateSwitching; + } + + @Override + public void updateSummary(DisplayModeDirector.VoteSummary summary) { + summary.disableRefreshRateSwitching = + summary.disableRefreshRateSwitching || mDisableRefreshRateSwitching; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DisableRefreshRateSwitchingVote that)) return false; + return mDisableRefreshRateSwitching == that.mDisableRefreshRateSwitching; + } + + @Override + public int hashCode() { + return Objects.hash(mDisableRefreshRateSwitching); + } + + @Override + public String toString() { + return "DisableRefreshRateSwitchingVote{ mDisableRefreshRateSwitching=" + + mDisableRefreshRateSwitching + " }"; + } +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index fb6c9e3cab9d..8eb03ec0d3bd 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -21,8 +21,8 @@ import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT; import static android.view.Display.Mode.INVALID_MODE_ID; +import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRate; import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE; -import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay; import android.annotation.IntegerRes; import android.annotation.NonNull; @@ -262,7 +262,7 @@ public class DisplayModeDirector { mVotesStorage.setLoggingEnabled(loggingEnabled); } - private static final class VoteSummary { + static final class VoteSummary { public float minPhysicalRefreshRate; public float maxPhysicalRefreshRate; public float minRenderFrameRate; @@ -274,7 +274,12 @@ public class DisplayModeDirector { public boolean disableRefreshRateSwitching; public float appRequestBaseModeRefreshRate; - VoteSummary() { + public List<SupportedModesVote.SupportedMode> supportedModes; + + final boolean mIsDisplayResolutionRangeVotingEnabled; + + VoteSummary(boolean isDisplayResolutionRangeVotingEnabled) { + mIsDisplayResolutionRangeVotingEnabled = isDisplayResolutionRangeVotingEnabled; reset(); } @@ -322,46 +327,7 @@ public class DisplayModeDirector { continue; } - // For physical refresh rates, just use the tightest bounds of all the votes. - // The refresh rate cannot be lower than the minimal render frame rate. - final float minPhysicalRefreshRate = Math.max(vote.refreshRateRanges.physical.min, - vote.refreshRateRanges.render.min); - summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate, - minPhysicalRefreshRate); - summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate, - vote.refreshRateRanges.physical.max); - - // Same goes to render frame rate, but frame rate cannot exceed the max physical - // refresh rate - final float maxRenderFrameRate = Math.min(vote.refreshRateRanges.render.max, - vote.refreshRateRanges.physical.max); - summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate, - vote.refreshRateRanges.render.min); - summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, maxRenderFrameRate); - - // For display size, disable refresh rate switching and base mode refresh rate use only - // the first vote we come across (i.e. the highest priority vote that includes the - // attribute). - if (vote.height > 0 && vote.width > 0) { - if (summary.width == Vote.INVALID_SIZE && summary.height == Vote.INVALID_SIZE) { - summary.width = vote.width; - summary.height = vote.height; - summary.minWidth = vote.minWidth; - summary.minHeight = vote.minHeight; - } else if (mIsDisplayResolutionRangeVotingEnabled) { - summary.width = Math.min(summary.width, vote.width); - summary.height = Math.min(summary.height, vote.height); - summary.minWidth = Math.max(summary.minWidth, vote.minWidth); - summary.minHeight = Math.max(summary.minHeight, vote.minHeight); - } - } - if (!summary.disableRefreshRateSwitching && vote.disableRefreshRateSwitching) { - summary.disableRefreshRateSwitching = true; - } - if (summary.appRequestBaseModeRefreshRate == 0f - && vote.appRequestBaseModeRefreshRate > 0f) { - summary.appRequestBaseModeRefreshRate = vote.appRequestBaseModeRefreshRate; - } + vote.updateSummary(summary); if (mLoggingEnabled) { Slog.w(TAG, "Vote summary for priority " + Vote.priorityToString(priority) @@ -443,7 +409,7 @@ public class DisplayModeDirector { ArrayList<Display.Mode> availableModes = new ArrayList<>(); availableModes.add(defaultMode); - VoteSummary primarySummary = new VoteSummary(); + VoteSummary primarySummary = new VoteSummary(mIsDisplayResolutionRangeVotingEnabled); int lowestConsideredPriority = Vote.MIN_PRIORITY; int highestConsideredPriority = Vote.MAX_PRIORITY; @@ -526,7 +492,7 @@ public class DisplayModeDirector { + "]"); } - VoteSummary appRequestSummary = new VoteSummary(); + VoteSummary appRequestSummary = new VoteSummary(mIsDisplayResolutionRangeVotingEnabled); summarizeVotes( votes, Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, @@ -959,11 +925,16 @@ public class DisplayModeDirector { } @VisibleForTesting + DisplayObserver getDisplayObserver() { + return mDisplayObserver; + } + + @VisibleForTesting DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings( float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) { synchronized (mLock) { - mSettingsObserver.updateRefreshRateSettingLocked( - minRefreshRate, peakRefreshRate, defaultRefreshRate); + mSettingsObserver.updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, + defaultRefreshRate, Display.DEFAULT_DISPLAY); return getDesiredDisplayModeSpecs(Display.DEFAULT_DISPLAY); } } @@ -1297,9 +1268,23 @@ public class DisplayModeDirector { mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode); } + /** + * Update refresh rate settings for all displays + */ private void updateRefreshRateSettingLocked() { + Display[] displays = mInjector.getDisplays(); + for (int i = 0; i < displays.length; i++) { + updateRefreshRateSettingLocked(displays[i].getDisplayId()); + } + } + + /** + * Update refresh rate settings for a specific display + * @param displayId The display ID + */ + private void updateRefreshRateSettingLocked(int displayId) { final ContentResolver cr = mContext.getContentResolver(); - float highestRefreshRate = findHighestRefreshRateForDefaultDisplay(mContext); + float highestRefreshRate = findHighestRefreshRate(mContext, displayId); float minRefreshRate = Settings.System.getFloatForUser(cr, Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId()); @@ -1327,11 +1312,12 @@ public class DisplayModeDirector { } } - updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate); + updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate, + displayId); } - private void updateRefreshRateSettingLocked( - float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) { + private void updateRefreshRateSettingLocked(float minRefreshRate, float peakRefreshRate, + float defaultRefreshRate, int displayId) { // TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is // used to predict if we're going to be doing frequent refresh rate switching, and if // so, enable the brightness observer. The logic here is more complicated and fragile @@ -1339,9 +1325,9 @@ public class DisplayModeDirector { Vote peakVote = peakRefreshRate == 0f ? null : Vote.forRenderFrameRates(0f, Math.max(minRefreshRate, peakRefreshRate)); - mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, + mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, peakVote); - mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, + mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(minRefreshRate, Float.POSITIVE_INFINITY)); Vote defaultVote = defaultRefreshRate == 0f @@ -1498,7 +1484,8 @@ public class DisplayModeDirector { } } - private final class DisplayObserver implements DisplayManager.DisplayListener { + @VisibleForTesting + public final class DisplayObserver implements DisplayManager.DisplayListener { // Note that we can never call into DisplayManager or any of the non-POD classes it // returns, while holding mLock since it may call into DMS, which might be simultaneously // calling into us already holding its own lock. @@ -1590,6 +1577,7 @@ public class DisplayModeDirector { updateDisplayModes(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); updateUserSettingDisplayPreferredSize(displayInfo); + mSettingsObserver.updateRefreshRateSettingLocked(displayId); } @Nullable diff --git a/services/core/java/com/android/server/display/mode/RefreshRateVote.java b/services/core/java/com/android/server/display/mode/RefreshRateVote.java new file mode 100644 index 000000000000..173b3c58cd95 --- /dev/null +++ b/services/core/java/com/android/server/display/mode/RefreshRateVote.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.mode; + +import java.util.Objects; + + +/** + * Information about the refresh rate frame rate ranges DM would like to set the display to. + */ +abstract class RefreshRateVote implements Vote { + final float mMinRefreshRate; + + final float mMaxRefreshRate; + + RefreshRateVote(float minRefreshRate, float maxRefreshRate) { + mMinRefreshRate = minRefreshRate; + mMaxRefreshRate = maxRefreshRate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof RefreshRateVote that)) return false; + return Float.compare(that.mMinRefreshRate, mMinRefreshRate) == 0 + && Float.compare(that.mMaxRefreshRate, mMaxRefreshRate) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(mMinRefreshRate, mMaxRefreshRate); + } + + @Override + public String toString() { + return "RefreshRateVote{ mMinRefreshRate=" + mMinRefreshRate + + ", mMaxRefreshRate=" + mMaxRefreshRate + " }"; + } + + static class RenderVote extends RefreshRateVote { + RenderVote(float minRefreshRate, float maxRefreshRate) { + super(minRefreshRate, maxRefreshRate); + } + + /** + * Summary: minRender minPhysical maxRender + * v v v + * -------------------|---------------------"-----------------------------|--------- + * ^ ^ ^* ^ ^ + * Vote: min(ignored) min(applied) min(applied+physical) max(applied) max(ignored) + */ + @Override + public void updateSummary(DisplayModeDirector.VoteSummary summary) { + summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate, mMinRefreshRate); + summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, mMaxRefreshRate); + // Physical refresh rate cannot be lower than the minimal render frame rate. + summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate, + mMinRefreshRate); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof RefreshRateVote.RenderVote)) return false; + return super.equals(o); + } + + @Override + public String toString() { + return "RenderVote{ " + super.toString() + " }"; + } + } + + static class PhysicalVote extends RefreshRateVote { + PhysicalVote(float minRefreshRate, float maxRefreshRate) { + super(minRefreshRate, maxRefreshRate); + } + + /** + * Summary: minPhysical maxRender maxPhysical + * v v v + * -------------------"-----------------------------|----------------------"---------- + * ^ ^ ^* ^ ^ + * Vote: min(ignored) min(applied) max(applied+render) max(applied) max(ignored) + */ + @Override + public void updateSummary(DisplayModeDirector.VoteSummary summary) { + summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate, + mMinRefreshRate); + summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate, + mMaxRefreshRate); + // Render frame rate cannot exceed the max physical refresh rate + summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, mMaxRefreshRate); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof RefreshRateVote.PhysicalVote)) return false; + return super.equals(o); + } + + @Override + public String toString() { + return "PhysicalVote{ " + super.toString() + " }"; + } + } +} diff --git a/services/core/java/com/android/server/display/mode/SizeVote.java b/services/core/java/com/android/server/display/mode/SizeVote.java new file mode 100644 index 000000000000..a9b18a54a105 --- /dev/null +++ b/services/core/java/com/android/server/display/mode/SizeVote.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.mode; + +import java.util.Objects; + +class SizeVote implements Vote { + + /** + * The requested width of the display in pixels; + */ + final int mWidth; + + /** + * The requested height of the display in pixels; + */ + final int mHeight; + + /** + * Min requested width of the display in pixels; + */ + final int mMinWidth; + + /** + * Min requested height of the display in pixels; + */ + final int mMinHeight; + + SizeVote(int width, int height, int minWidth, int minHeight) { + mWidth = width; + mHeight = height; + mMinWidth = minWidth; + mMinHeight = minHeight; + } + + @Override + public void updateSummary(DisplayModeDirector.VoteSummary summary) { + if (mHeight > 0 && mWidth > 0) { + // For display size, disable refresh rate switching and base mode refresh rate use + // only the first vote we come across (i.e. the highest priority vote that includes + // the attribute). + if (summary.width == Vote.INVALID_SIZE && summary.height == Vote.INVALID_SIZE) { + summary.width = mWidth; + summary.height = mHeight; + summary.minWidth = mMinWidth; + summary.minHeight = mMinHeight; + } else if (summary.mIsDisplayResolutionRangeVotingEnabled) { + summary.width = Math.min(summary.width, mWidth); + summary.height = Math.min(summary.height, mHeight); + summary.minWidth = Math.max(summary.minWidth, mMinWidth); + summary.minHeight = Math.max(summary.minHeight, mMinHeight); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SizeVote sizeVote)) return false; + return mWidth == sizeVote.mWidth && mHeight == sizeVote.mHeight + && mMinWidth == sizeVote.mMinWidth && mMinHeight == sizeVote.mMinHeight; + } + + @Override + public int hashCode() { + return Objects.hash(mWidth, mHeight, mMinWidth, mMinHeight); + } + + @Override + public String toString() { + return "SizeVote{ mWidth=" + mWidth + ", mHeight=" + mHeight + + ", mMinWidth=" + mMinWidth + ", mMinHeight=" + mMinHeight + " }"; + } +} diff --git a/services/core/java/com/android/server/display/mode/SupportedModesVote.java b/services/core/java/com/android/server/display/mode/SupportedModesVote.java new file mode 100644 index 000000000000..b31461fc66b8 --- /dev/null +++ b/services/core/java/com/android/server/display/mode/SupportedModesVote.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.mode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +class SupportedModesVote implements Vote { + + final List<SupportedMode> mSupportedModes; + + SupportedModesVote(List<SupportedMode> supportedModes) { + mSupportedModes = Collections.unmodifiableList(supportedModes); + } + + /** + * Summary should have subset of supported modes. + * If Vote1.supportedModes=(A,B), Vote2.supportedModes=(B,C) then summary.supportedModes=(B) + * If summary.supportedModes==null then there is no restriction on supportedModes + */ + @Override + public void updateSummary(DisplayModeDirector.VoteSummary summary) { + if (summary.supportedModes == null) { + summary.supportedModes = new ArrayList<>(mSupportedModes); + } else { + summary.supportedModes.retainAll(mSupportedModes); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SupportedModesVote that)) return false; + return mSupportedModes.equals(that.mSupportedModes); + } + + @Override + public int hashCode() { + return Objects.hash(mSupportedModes); + } + + @Override + public String toString() { + return "SupportedModesVote{ mSupportedModes=" + mSupportedModes + " }"; + } + + static class SupportedMode { + final float mPeakRefreshRate; + final float mVsyncRate; + + + SupportedMode(float peakRefreshRate, float vsyncRate) { + mPeakRefreshRate = peakRefreshRate; + mVsyncRate = vsyncRate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SupportedMode that)) return false; + return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0 + && Float.compare(that.mVsyncRate, mVsyncRate) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(mPeakRefreshRate, mVsyncRate); + } + + @Override + public String toString() { + return "SupportedMode{ mPeakRefreshRate=" + mPeakRefreshRate + + ", mVsyncRate=" + mVsyncRate + " }"; + } + } +} diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java index b6a6069b5a63..c1cdd6952dcc 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -16,15 +16,13 @@ package com.android.server.display.mode; -import android.view.SurfaceControl; +import java.util.List; -import java.util.Objects; - -final class Vote { +interface Vote { // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest // priority vote, it's overridden by all other considerations. It acts to set a default // frame rate for a device. - static final int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0; + int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0; // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or // null. It is used to set a preferred refresh rate value in case the higher priority votes @@ -32,21 +30,21 @@ final class Vote { static final int PRIORITY_FLICKER_REFRESH_RATE = 1; // High-brightness-mode may need a specific range of refresh-rates to function properly. - static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2; + int PRIORITY_HIGH_BRIGHTNESS_MODE = 2; // SETTING_MIN_RENDER_FRAME_RATE is used to propose a lower bound of the render frame rate. // It votes [minRefreshRate, Float.POSITIVE_INFINITY] - static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3; + int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3; // User setting preferred display resolution. - static final int PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE = 4; + int PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE = 4; // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render // frame rate in certain cases, mostly to preserve power. // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate]. - static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 5; + int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 5; // We split the app request into different priorities in case we can satisfy one desire // without the other. @@ -72,181 +70,100 @@ final class Vote { // The preferred refresh rate is set on the main surface of the app outside of // DisplayModeDirector. // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded - static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 6; + int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 6; - static final int PRIORITY_APP_REQUEST_SIZE = 7; + int PRIORITY_APP_REQUEST_SIZE = 7; // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the // rest of low priority voters. It votes [0, max(PEAK, MIN)] - static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 8; + int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 8; // Restrict all displays to 60Hz when external display is connected. It votes [59Hz, 61Hz]. - static final int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 9; + int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 9; // Restrict displays max available resolution and refresh rates. It votes [0, LIMIT] - static final int PRIORITY_LIMIT_MODE = 10; + int PRIORITY_LIMIT_MODE = 10; // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh // rate to max value (same as for PRIORITY_UDFPS) on lock screen - static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 11; + int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 11; // For concurrent displays we want to limit refresh rate on all displays - static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12; + int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12; // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - static final int PRIORITY_LOW_POWER_MODE = 13; + int PRIORITY_LOW_POWER_MODE = 13; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. // It's used to avoid refresh rate switches in certain conditions which may result in the // user seeing the display flickering when the switches occur. - static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 14; + int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 14; // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. - static final int PRIORITY_SKIN_TEMPERATURE = 15; + int PRIORITY_SKIN_TEMPERATURE = 15; // The proximity sensor needs the refresh rate to be locked in order to function, so this is // set to a high priority. - static final int PRIORITY_PROXIMITY = 16; + int PRIORITY_PROXIMITY = 16; // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order // to function, so this needs to be the highest priority of all votes. - static final int PRIORITY_UDFPS = 17; + int PRIORITY_UDFPS = 17; // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString. - static final int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE; - static final int MAX_PRIORITY = PRIORITY_UDFPS; + int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE; + int MAX_PRIORITY = PRIORITY_UDFPS; // The cutoff for the app request refresh rate range. Votes with priorities lower than this // value will not be considered when constructing the app request refresh rate range. - static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF = + int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF = PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE; /** * A value signifying an invalid width or height in a vote. */ - static final int INVALID_SIZE = -1; + int INVALID_SIZE = -1; - /** - * The requested width of the display in pixels, or INVALID_SIZE; - */ - public final int width; - /** - * The requested height of the display in pixels, or INVALID_SIZE; - */ - public final int height; - /** - * Min requested width of the display in pixels, or 0; - */ - public final int minWidth; - /** - * Min requested height of the display in pixels, or 0; - */ - public final int minHeight; - /** - * Information about the refresh rate frame rate ranges DM would like to set the display to. - */ - public final SurfaceControl.RefreshRateRanges refreshRateRanges; - - /** - * Whether refresh rate switching should be disabled (i.e. the refresh rate range is - * a single value). - */ - public final boolean disableRefreshRateSwitching; - - /** - * The preferred refresh rate selected by the app. It is used to validate that the summary - * refresh rate ranges include this value, and are not restricted by a lower priority vote. - */ - public final float appRequestBaseModeRefreshRate; + void updateSummary(DisplayModeDirector.VoteSummary summary); static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) { - return new Vote(/* minWidth= */ 0, /* minHeight= */ 0, - /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE, - /* minPhysicalRefreshRate= */ minRefreshRate, - /* maxPhysicalRefreshRate= */ maxRefreshRate, - /* minRenderFrameRate= */ 0, - /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY, - /* disableRefreshRateSwitching= */ minRefreshRate == maxRefreshRate, - /* baseModeRefreshRate= */ 0f); + return new CombinedVote( + List.of( + new RefreshRateVote.PhysicalVote(minRefreshRate, maxRefreshRate), + new DisableRefreshRateSwitchingVote(minRefreshRate == maxRefreshRate) + ) + ); } static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) { - return new Vote(/* minWidth= */ 0, /* minHeight= */ 0, - /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE, - /* minPhysicalRefreshRate= */ 0, - /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY, - minFrameRate, - maxFrameRate, - /* disableRefreshRateSwitching= */ false, - /* baseModeRefreshRate= */ 0f); + return new RefreshRateVote.RenderVote(minFrameRate, maxFrameRate); } static Vote forSize(int width, int height) { - return new Vote(/* minWidth= */ width, /* minHeight= */ height, - width, height, - /* minPhysicalRefreshRate= */ 0, - /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY, - /* minRenderFrameRate= */ 0, - /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY, - /* disableRefreshRateSwitching= */ false, - /* baseModeRefreshRate= */ 0f); + return new SizeVote(width, height, width, height); } static Vote forSizeAndPhysicalRefreshRatesRange(int minWidth, int minHeight, int width, int height, float minRefreshRate, float maxRefreshRate) { - return new Vote(minWidth, minHeight, - width, height, - minRefreshRate, - maxRefreshRate, - /* minRenderFrameRate= */ 0, - /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY, - /* disableRefreshRateSwitching= */ minRefreshRate == maxRefreshRate, - /* baseModeRefreshRate= */ 0f); + return new CombinedVote( + List.of( + new SizeVote(width, height, minWidth, minHeight), + new RefreshRateVote.PhysicalVote(minRefreshRate, maxRefreshRate), + new DisableRefreshRateSwitchingVote(minRefreshRate == maxRefreshRate) + ) + ); } static Vote forDisableRefreshRateSwitching() { - return new Vote(/* minWidth= */ 0, /* minHeight= */ 0, - /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE, - /* minPhysicalRefreshRate= */ 0, - /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY, - /* minRenderFrameRate= */ 0, - /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY, - /* disableRefreshRateSwitching= */ true, - /* baseModeRefreshRate= */ 0f); + return new DisableRefreshRateSwitchingVote(true); } static Vote forBaseModeRefreshRate(float baseModeRefreshRate) { - return new Vote(/* minWidth= */ 0, /* minHeight= */ 0, - /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE, - /* minPhysicalRefreshRate= */ 0, - /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY, - /* minRenderFrameRate= */ 0, - /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY, - /* disableRefreshRateSwitching= */ false, - /* baseModeRefreshRate= */ baseModeRefreshRate); - } - - private Vote(int minWidth, int minHeight, - int width, int height, - float minPhysicalRefreshRate, - float maxPhysicalRefreshRate, - float minRenderFrameRate, - float maxRenderFrameRate, - boolean disableRefreshRateSwitching, - float baseModeRefreshRate) { - this.minWidth = minWidth; - this.minHeight = minHeight; - this.width = width; - this.height = height; - this.refreshRateRanges = new SurfaceControl.RefreshRateRanges( - new SurfaceControl.RefreshRateRange(minPhysicalRefreshRate, maxPhysicalRefreshRate), - new SurfaceControl.RefreshRateRange(minRenderFrameRate, maxRenderFrameRate)); - this.disableRefreshRateSwitching = disableRefreshRateSwitching; - this.appRequestBaseModeRefreshRate = baseModeRefreshRate; + return new BaseModeRefreshRateVote(baseModeRefreshRate); } static String priorityToString(int priority) { @@ -291,33 +208,4 @@ final class Vote { return Integer.toString(priority); } } - - @Override - public String toString() { - return "Vote: {" - + "minWidth: " + minWidth + ", minHeight: " + minHeight - + ", width: " + width + ", height: " + height - + ", refreshRateRanges: " + refreshRateRanges - + ", disableRefreshRateSwitching: " + disableRefreshRateSwitching - + ", appRequestBaseModeRefreshRate: " + appRequestBaseModeRefreshRate + "}"; - } - - @Override - public int hashCode() { - return Objects.hash(minWidth, minHeight, width, height, refreshRateRanges, - disableRefreshRateSwitching, appRequestBaseModeRefreshRate); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Vote)) return false; - final var vote = (Vote) o; - return minWidth == vote.minWidth && minHeight == vote.minHeight - && width == vote.width && height == vote.height - && disableRefreshRateSwitching == vote.disableRefreshRateSwitching - && Float.compare(vote.appRequestBaseModeRefreshRate, - appRequestBaseModeRefreshRate) == 0 - && refreshRateRanges.equals(vote.refreshRateRanges); - } } diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java index 49c587aa5596..95fb8fc0947a 100644 --- a/services/core/java/com/android/server/display/mode/VotesStorage.java +++ b/services/core/java/com/android/server/display/mode/VotesStorage.java @@ -157,13 +157,19 @@ class VotesStorage { } } - private int getMaxPhysicalRefreshRate(@Nullable Vote vote) { + private static int getMaxPhysicalRefreshRate(@Nullable Vote vote) { if (vote == null) { return -1; - } else if (vote.refreshRateRanges.physical.max == Float.POSITIVE_INFINITY) { - return 1000; // for visualisation, otherwise e.g. -1 -> 60 will be unnoticeable + } else if (vote instanceof RefreshRateVote.PhysicalVote physicalVote) { + return (int) physicalVote.mMaxRefreshRate; + } else if (vote instanceof CombinedVote combinedVote) { + return combinedVote.mVotes.stream() + .filter(v -> v instanceof RefreshRateVote.PhysicalVote) + .map(pv -> (int) (((RefreshRateVote.PhysicalVote) pv).mMaxRefreshRate)) + .min(Integer::compare) + .orElse(1000); // for visualisation } - return (int) vote.refreshRateRanges.physical.max; + return 1000; // for visualisation, otherwise e.g. -1 -> 60 will be unnoticeable } interface Listener { diff --git a/services/core/java/com/android/server/flags/OWNERS b/services/core/java/com/android/server/flags/OWNERS new file mode 100644 index 000000000000..535a7509601c --- /dev/null +++ b/services/core/java/com/android/server/flags/OWNERS @@ -0,0 +1 @@ +per-file pinner.aconfig = edgararriaga@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/flags/pinner.aconfig b/services/core/java/com/android/server/flags/pinner.aconfig new file mode 100644 index 000000000000..606a6be29511 --- /dev/null +++ b/services/core/java/com/android/server/flags/pinner.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.flags" + +flag { + name: "pin_webview" + namespace: "system_performance" + description: "This flag controls if webview should be pinned in memory." + bug: "307594624" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 6a43697770cf..4821fbe1e6c0 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -2486,6 +2486,13 @@ class MediaRouter2ServiceImpl { private void onRequestFailedOnHandler(@NonNull MediaRoute2Provider provider, long uniqueRequestId, int reason) { if (handleSessionCreationRequestFailed(provider, uniqueRequestId, reason)) { + Slog.w( + TAG, + TextUtils.formatSimple( + "onRequestFailedOnHandler | Finished handling session creation" + + " request failed for provider: %s, uniqueRequestId: %d," + + " reason: %d", + provider.getUniqueId(), uniqueRequestId, reason)); return; } @@ -2515,6 +2522,12 @@ class MediaRouter2ServiceImpl { if (matchingRequest == null) { // The failure is not about creating a session. + Slog.w( + TAG, + TextUtils.formatSimple( + "handleSessionCreationRequestFailed | No matching request found for" + + " provider: %s, uniqueRequestId: %d, reason: %d", + provider.getUniqueId(), uniqueRequestId, reason)); return false; } diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index fe91050917f5..d0c054307d0c 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -1249,6 +1249,21 @@ abstract public class ManagedServices { } } } + // Remove uninstalled components from user-set list + final ArraySet<String> userSet = mUserSetServices.get(uninstalledUserId); + if (userSet != null) { + int numServices = userSet.size(); + for (int i = numServices - 1; i >= 0; i--) { + String pkgOrComponent = userSet.valueAt(i); + if (TextUtils.equals(pkg, getPackageName(pkgOrComponent))) { + userSet.removeAt(i); + if (DEBUG) { + Slog.v(TAG, "Removing " + pkgOrComponent + + " from user-set list; uninstalled"); + } + } + } + } } return removed; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c2b59644ce1c..aa0b9b892220 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1765,8 +1765,7 @@ public class NotificationManagerService extends SystemService { if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { // update system notification channels SystemNotificationChannels.createAll(context); - mZenModeHelper.updateDefaultZenRules(Binder.getCallingUid(), - isCallerIsSystemOrSystemUi()); + mZenModeHelper.updateDefaultZenRules(Binder.getCallingUid()); mPreferencesHelper.onLocaleChanged(context, ActivityManager.getCurrentUser()); } } @@ -3039,7 +3038,7 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), true); mPreferencesHelper.updateNotificationChannel(pkg, uid, channel, true, - Binder.getCallingUid(), isCallerIsSystemOrSystemUi()); + Binder.getCallingUid(), isCallerSystemOrSystemUi()); if (mPreferencesHelper.onlyHasDefaultChannel(pkg, uid)) { mPermissionHelper.setNotificationPermission(pkg, UserHandle.getUserId(uid), channel.getImportance() != IMPORTANCE_NONE, true); @@ -3087,7 +3086,7 @@ public class NotificationManagerService extends SystemService { final NotificationChannelGroup preUpdate = mPreferencesHelper.getNotificationChannelGroup(group.getId(), pkg, uid); mPreferencesHelper.createNotificationChannelGroup(pkg, uid, group, - fromApp, Binder.getCallingUid(), isCallerIsSystemOrSystemUi()); + fromApp, Binder.getCallingUid(), isCallerSystemOrSystemUi()); if (!fromApp) { maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group); } @@ -3517,7 +3516,7 @@ public class NotificationManagerService extends SystemService { } checkCallerIsSameApp(pkg); - final boolean isSystemToast = isCallerIsSystemOrSystemUi() + final boolean isSystemToast = isCallerSystemOrSystemUi() || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg); boolean isAppRenderedToast = (callback != null); if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast, @@ -4084,7 +4083,7 @@ public class NotificationManagerService extends SystemService { channel, true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed( pkg, UserHandle.getUserId(uid)), Binder.getCallingUid(), - isCallerIsSystemOrSystemUi()); + isCallerSystemOrSystemUi()); if (needsPolicyFileChange) { mListeners.notifyNotificationChannelChanged(pkg, UserHandle.getUserHandleForUid(uid), @@ -4165,7 +4164,7 @@ public class NotificationManagerService extends SystemService { String targetPkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId) { if (canNotifyAsPackage(callingPkg, targetPkg, userId) - || isCallerIsSystemOrSysemUiOrShell()) { + || isCallerSystemOrSystemUiOrShell()) { int targetUid = -1; try { targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId); @@ -4220,7 +4219,7 @@ public class NotificationManagerService extends SystemService { public void deleteNotificationChannel(String pkg, String channelId) { checkCallerIsSystemOrSameApp(pkg); final int callingUid = Binder.getCallingUid(); - final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi(); + final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi(); final int callingUser = UserHandle.getUserId(callingUid); if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { throw new IllegalArgumentException("Cannot delete default channel"); @@ -4264,7 +4263,7 @@ public class NotificationManagerService extends SystemService { checkCallerIsSystemOrSameApp(pkg); final int callingUid = Binder.getCallingUid(); - final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi(); + final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi(); NotificationChannelGroup groupToDelete = mPreferencesHelper.getNotificationChannelGroupWithChannels( pkg, callingUid, groupId, false); @@ -4473,6 +4472,10 @@ public class NotificationManagerService extends SystemService { @Override public boolean areChannelsBypassingDnd() { + if (android.app.Flags.modesApi()) { + return mZenModeHelper.getConsolidatedNotificationPolicy().allowPriorityChannels() + && mPreferencesHelper.areChannelsBypassingDnd(); + } return mPreferencesHelper.areChannelsBypassingDnd(); } @@ -5203,7 +5206,7 @@ public class NotificationManagerService extends SystemService { public void requestInterruptionFilterFromListener(INotificationListener token, int interruptionFilter) throws RemoteException { final int callingUid = Binder.getCallingUid(); - final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi(); + final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi(); final long identity = Binder.clearCallingIdentity(); try { synchronized (mNotificationLock) { @@ -5250,7 +5253,7 @@ public class NotificationManagerService extends SystemService { public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException { enforceSystemOrSystemUI("INotificationManager.setZenMode"); final int callingUid = Binder.getCallingUid(); - final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi(); + final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi(); final long identity = Binder.clearCallingIdentity(); try { mZenModeHelper.setManualZenMode(mode, conditionId, null, reason, callingUid, @@ -5312,7 +5315,9 @@ public class NotificationManagerService extends SystemService { return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule, "addAutomaticZenRule", Binder.getCallingUid(), - isCallerIsSystemOrSystemUi()); + // TODO: b/308670715: Distinguish FROM_APP from FROM_USER + isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI + : ZenModeHelper.FROM_APP); } @Override @@ -5330,7 +5335,9 @@ public class NotificationManagerService extends SystemService { return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule, "updateAutomaticZenRule", Binder.getCallingUid(), - isCallerIsSystemOrSystemUi()); + // TODO: b/308670715: Distinguish FROM_APP from FROM_USER + isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI + : ZenModeHelper.FROM_APP); } @Override @@ -5340,7 +5347,7 @@ public class NotificationManagerService extends SystemService { enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule"); return mZenModeHelper.removeAutomaticZenRule(id, "removeAutomaticZenRule", - Binder.getCallingUid(), isCallerIsSystemOrSystemUi()); + Binder.getCallingUid(), isCallerSystemOrSystemUi()); } @Override @@ -5350,7 +5357,7 @@ public class NotificationManagerService extends SystemService { return mZenModeHelper.removeAutomaticZenRules(packageName, packageName + "|removeAutomaticZenRules", Binder.getCallingUid(), - isCallerIsSystemOrSystemUi()); + isCallerSystemOrSystemUi()); } @Override @@ -5369,7 +5376,7 @@ public class NotificationManagerService extends SystemService { enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState"); mZenModeHelper.setAutomaticZenRuleState(id, condition, Binder.getCallingUid(), - isCallerIsSystemOrSystemUi()); + isCallerSystemOrSystemUi()); } @Override @@ -5378,7 +5385,7 @@ public class NotificationManagerService extends SystemService { final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1); if (zen == -1) throw new IllegalArgumentException("Invalid filter: " + filter); final int callingUid = Binder.getCallingUid(); - final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi(); + final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi(); if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) { mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, callingUid, zen); @@ -5473,7 +5480,7 @@ public class NotificationManagerService extends SystemService { () -> CompatChanges.isChangeEnabled(MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES, callingUid)); return !isCompatChangeEnabled - || isCallerIsSystemOrSystemUi() + || isCallerSystemOrSystemUi() || hasCompanionDevice(callingPkg, UserHandle.getUserId(callingUid), AssociationRequest.DEVICE_PROFILE_WATCH); } @@ -5704,7 +5711,7 @@ public class NotificationManagerService extends SystemService { public void setNotificationPolicy(String pkg, Policy policy) { enforcePolicyAccess(pkg, "setNotificationPolicy"); int callingUid = Binder.getCallingUid(); - boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi(); + boolean isSystemOrSystemUi = isCallerSystemOrSystemUi(); boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid); @@ -7106,7 +7113,7 @@ public class NotificationManagerService extends SystemService { } mPreferencesHelper.updateNotificationChannel( pkg, notificationUid, channel, false, callingUid, - isCallerIsSystemOrSystemUi()); + isCallerSystemOrSystemUi()); r.updateNotificationChannel(channel); } else if (!channel.isUserVisibleTaskShown() && !TextUtils.isEmpty(channelId) && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { @@ -10426,7 +10433,7 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting - protected boolean isCallerIsSystemOrSystemUi() { + protected boolean isCallerSystemOrSystemUi() { if (isCallerSystemOrPhone()) { return true; } @@ -10434,12 +10441,12 @@ public class NotificationManagerService extends SystemService { == PERMISSION_GRANTED; } - private boolean isCallerIsSystemOrSysemUiOrShell() { + private boolean isCallerSystemOrSystemUiOrShell() { int callingUid = Binder.getCallingUid(); if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { return true; } - return isCallerIsSystemOrSystemUi(); + return isCallerSystemOrSystemUi(); } private void checkCallerIsSystemOrShell() { diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 252664a7e4e4..6a7eebb32c8b 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -200,6 +200,10 @@ public class PreferencesHelper implements RankingConfig { private SparseBooleanArray mLockScreenShowNotifications; private SparseBooleanArray mLockScreenPrivateNotifications; private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING; + // When modes_api flag is enabled, this value only tracks whether the current user has any + // channels marked as "priority channels", but not necessarily whether they are permitted + // to bypass DND by current zen policy. + // TODO: b/310620812 - Rename to be more accurate when modes_api flag is inlined. private boolean mCurrentUserHasChannelsBypassingDnd; private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS; private final boolean mShowReviewPermissionsNotification; @@ -1866,6 +1870,7 @@ public class PreferencesHelper implements RankingConfig { policy.priorityConversationSenders), callingUid, fromSystemOrSystemUi); } + // TODO: b/310620812 - rename to hasPriorityChannels() when modes_api is inlined. public boolean areChannelsBypassingDnd() { return mCurrentUserHasChannelsBypassingDnd; } diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java index f56a67c00c15..ff263d1467c3 100644 --- a/services/core/java/com/android/server/notification/ZenModeFiltering.java +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -19,6 +19,7 @@ package com.android.server.notification; import static android.provider.Settings.Global.ZEN_MODE_OFF; import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE; +import android.app.Flags; import android.app.Notification; import android.app.NotificationManager; import android.content.ComponentName; @@ -144,6 +145,20 @@ public class ZenModeFiltering { REPEAT_CALLERS.recordCall(mContext, extras(record), record.getPhoneNumbers()); } + // Returns whether the record is permitted to bypass DND when the zen mode is + // ZEN_MODE_IMPORTANT_INTERRUPTIONS. This depends on whether the record's package priority is + // marked as PRIORITY_MAX (an indication of it belonging to a priority channel), and, if + // the modes_api flag is on, whether the given policy permits priority channels to bypass. + // TODO: b/310620812 - simplify when modes_api is inlined. + private boolean canRecordBypassDnd(NotificationRecord record, + NotificationManager.Policy policy) { + boolean inPriorityChannel = record.getPackagePriority() == Notification.PRIORITY_MAX; + if (Flags.modesApi()) { + return inPriorityChannel && policy.allowPriorityChannels(); + } + return inPriorityChannel; + } + /** * Whether to intercept the notification based on the policy */ @@ -180,7 +195,7 @@ public class ZenModeFiltering { return true; case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: // allow user-prioritized packages through in priority mode - if (record.getPackagePriority() == Notification.PRIORITY_MAX) { + if (canRecordBypassDnd(record, policy)) { maybeLogInterceptDecision(record, false, "priorityApp"); return false; } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index cb05084ea3f0..89d820050b03 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -27,6 +27,7 @@ import static android.service.notification.NotificationServiceProto.ROOT_CONFIG; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; @@ -73,6 +74,7 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.ConditionProviderService; +import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ZenRule; import android.service.notification.ZenModeProto; @@ -105,6 +107,8 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -129,6 +133,21 @@ public class ZenModeHelper { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) static final long SEND_ACTIVATION_AZR_STATUSES = 308673617L; + /** A rule addition or update that is initiated by the System or SystemUI. */ + static final int FROM_SYSTEM_OR_SYSTEMUI = 1; + /** A rule addition or update that is initiated by the user (through system settings). */ + static final int FROM_USER = 2; + /** A rule addition or update that is initiated by an app (via NotificationManager APIs). */ + static final int FROM_APP = 3; + + @IntDef(prefix = { "FROM_" }, value = { + FROM_SYSTEM_OR_SYSTEMUI, + FROM_USER, + FROM_APP + }) + @Retention(RetentionPolicy.SOURCE) + @interface ChangeOrigin {} + // pkg|userId => uid @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>(); @@ -378,7 +397,7 @@ public class ZenModeHelper { } public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule, - String reason, int callingUid, boolean fromSystemOrSystemUi) { + String reason, int callingUid, @ChangeOrigin int origin) { if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) { PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner()); if (component == null) { @@ -412,10 +431,10 @@ public class ZenModeHelper { } newConfig = mConfig.copy(); ZenRule rule = new ZenRule(); - populateZenRule(pkg, automaticZenRule, rule, true); + populateZenRule(pkg, automaticZenRule, rule, true, origin); newConfig.automaticRules.put(rule.id, rule); if (setConfigLocked(newConfig, reason, rule.component, true, callingUid, - fromSystemOrSystemUi)) { + origin == FROM_SYSTEM_OR_SYSTEMUI)) { return rule.id; } else { throw new AndroidRuntimeException("Could not create rule"); @@ -424,7 +443,7 @@ public class ZenModeHelper { } public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule, - String reason, int callingUid, boolean fromSystemOrSystemUi) { + String reason, int callingUid, @ChangeOrigin int origin) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -452,9 +471,9 @@ public class ZenModeHelper { } } - populateZenRule(rule.pkg, automaticZenRule, rule, false); + populateZenRule(rule.pkg, automaticZenRule, rule, false, origin); return setConfigLocked(newConfig, reason, rule.component, true, callingUid, - fromSystemOrSystemUi); + origin == FROM_SYSTEM_OR_SYSTEMUI); } } @@ -790,7 +809,7 @@ public class ZenModeHelper { } } - protected void updateDefaultZenRules(int callingUid, boolean fromSystemOrSystemUi) { + protected void updateDefaultZenRules(int callingUid) { updateDefaultAutomaticRuleNames(); synchronized (mConfigLock) { for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) { @@ -807,7 +826,7 @@ public class ZenModeHelper { // update default rule (if locale changed, name of rule will change) currRule.name = defaultRule.name; updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule), - "locale changed", callingUid, fromSystemOrSystemUi); + "locale changed", callingUid, FROM_SYSTEM_OR_SYSTEMUI); } } } @@ -850,7 +869,11 @@ public class ZenModeHelper { } private static void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, - boolean isNew) { + boolean isNew, @ChangeOrigin int origin) { + // TODO: b/308671593,b/311406021 - Handle origins more precisely: + // - FROM_USER can override anything and updates bitmask of user-modified fields; + // - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask; + // - FROM_APP can only update if not user-modified. if (rule.enabled != automaticZenRule.isEnabled()) { rule.snoozing = false; } @@ -861,7 +884,10 @@ public class ZenModeHelper { rule.modified = automaticZenRule.isModified(); rule.zenPolicy = automaticZenRule.getZenPolicy(); if (Flags.modesApi()) { - rule.zenDeviceEffects = automaticZenRule.getDeviceEffects(); + rule.zenDeviceEffects = fixZenDeviceEffects( + rule.zenDeviceEffects, + automaticZenRule.getDeviceEffects(), + origin); } rule.zenMode = NotificationManager.zenModeFromInterruptionFilter( automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF); @@ -882,6 +908,50 @@ public class ZenModeHelper { } } + /** " + * Fix" {@link ZenDeviceEffects} that are being stored as part of a new or updated ZenRule. + * + * <ul> + * <li> Apps cannot turn on hidden effects (those tagged as {@code @hide}) since they are + * intended for platform-specific rules (e.g. wearables). If it's a new rule, we blank them + * out; if it's an update, we preserve the previous values. + * </ul> + */ + @Nullable + private static ZenDeviceEffects fixZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects, + @Nullable ZenDeviceEffects newEffects, @ChangeOrigin int origin) { + // TODO: b/308671593,b/311406021 - Handle origins more precisely: + // - FROM_USER can override anything and updates bitmask of user-modified fields; + // - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask; + // - FROM_APP can only update if not user-modified. + if (origin == FROM_SYSTEM_OR_SYSTEMUI || origin == FROM_USER) { + return newEffects; + } + + if (newEffects == null) { + return null; + } + if (oldEffects != null) { + return new ZenDeviceEffects.Builder(newEffects) + .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness()) + .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake()) + .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake()) + .setShouldDisableTouch(oldEffects.shouldDisableTouch()) + .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage()) + .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze()) + .build(); + } else { + return new ZenDeviceEffects.Builder(newEffects) + .setShouldDisableAutoBrightness(false) + .setShouldDisableTapToWake(false) + .setShouldDisableTiltToWake(false) + .setShouldDisableTouch(false) + .setShouldMinimizeRadioUsage(false) + .setShouldMaximizeDoze(false) + .build(); + } + } + private static AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) { AutomaticZenRule azr; if (Flags.modesApi()) { @@ -1020,7 +1090,7 @@ public class ZenModeHelper { } pw.printf("allow(alarms=%b,media=%b,system=%b,calls=%b,callsFrom=%s,repeatCallers=%b," + "messages=%b,messagesFrom=%s,conversations=%b,conversationsFrom=%s," - + "events=%b,reminders=%b)\n", + + "events=%b,reminders=%b", config.allowAlarms, config.allowMedia, config.allowSystem, config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom), config.allowRepeatCallers, config.allowMessages, @@ -1028,6 +1098,10 @@ public class ZenModeHelper { config.allowConversations, ZenPolicy.conversationTypeToString(config.allowConversationsFrom), config.allowEvents, config.allowReminders); + if (Flags.modesApi()) { + pw.printf(",priorityChannels=%b", config.allowPriorityChannels); + } + pw.printf(")\n"); pw.print(prefix); pw.printf(" disallow(visualEffects=%s)\n", config.suppressedVisualEffects); pw.print(prefix); pw.print(" manualRule="); pw.println(config.manualRule); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index d46d55977d2f..448f215bb029 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -154,6 +154,7 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.F2fsUtils; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.pm.pkg.component.ParsedIntentInfo; @@ -175,7 +176,6 @@ import com.android.server.pm.dex.DexoptOptions; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.Permission; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; @@ -3349,9 +3349,7 @@ final class InstallPackageHelper { if (disabledPs == null) { logCriticalInfo(Log.WARN, "System package " + packageName + " no longer exists; its data will be wiped"); - mInjector.getHandler().post( - () -> mRemovePackageHelper.removePackageData(ps, userIds)); - expectingBetter.put(ps.getPackageName(), ps.getPath()); + mRemovePackageHelper.removePackageData(ps, userIds); } else { // we still have a disabled system package, but, it still might have // been removed. check the code path still exists and check there's diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index fc831205f60e..0a81b2b9fabb 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -50,9 +50,9 @@ import android.util.ArrayMap; import android.util.ExceptionUtils; import android.util.Slog; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.util.ArrayUtils; import com.android.server.art.model.DexoptResult; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 3f4cc4a84ffd..b80c0094ffb9 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1531,7 +1531,8 @@ public class LauncherAppsService extends SystemService { throw new ActivityNotFoundException("Activity could not be found"); } - final Intent launchIntent = getMainActivityLaunchIntent(component, user); + final Intent launchIntent = getMainActivityLaunchIntent(component, user, + false /* includeArchivedApps */); if (launchIntent == null) { throw new SecurityException("Attempt to launch activity without " + " category Intent.CATEGORY_LAUNCHER " + component); @@ -1577,7 +1578,8 @@ public class LauncherAppsService extends SystemService { return; } - Intent launchIntent = getMainActivityLaunchIntent(component, user); + Intent launchIntent = getMainActivityLaunchIntent(component, user, + true /* includeArchivedApps */); if (launchIntent == null) { throw new SecurityException("Attempt to launch activity without " + " category Intent.CATEGORY_LAUNCHER " + component); @@ -1593,7 +1595,8 @@ public class LauncherAppsService extends SystemService { /** * Returns the main activity launch intent for the given component package. */ - private Intent getMainActivityLaunchIntent(ComponentName component, UserHandle user) { + private Intent getMainActivityLaunchIntent(ComponentName component, UserHandle user, + boolean includeArchivedApps) { Intent launchIntent = new Intent(Intent.ACTION_MAIN); launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK @@ -1632,6 +1635,14 @@ public class LauncherAppsService extends SystemService { break; } } + if (!canLaunch + && includeArchivedApps + && Flags.archiving() + && getMatchingArchivedAppActivityInfo(component, user) != null) { + launchIntent.setPackage(null); + launchIntent.setComponent(component); + canLaunch = true; + } if (!canLaunch) { return null; } diff --git a/services/core/java/com/android/server/pm/PackageAbiHelper.java b/services/core/java/com/android/server/pm/PackageAbiHelper.java index 6faf68dc560c..c66a9e98c1d3 100644 --- a/services/core/java/com/android/server/pm/PackageAbiHelper.java +++ b/services/core/java/com/android/server/pm/PackageAbiHelper.java @@ -22,7 +22,7 @@ import android.util.ArraySet; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 968be5c2cf1c..c6e8a6463121 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -16,6 +16,10 @@ package com.android.server.pm; +import static android.app.ActivityManager.START_ABORTED; +import static android.app.ActivityManager.START_CLASS_NOT_FOUND; +import static android.app.ActivityManager.START_PERMISSION_DENIED; +import static android.app.ActivityManager.START_SUCCESS; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap; @@ -34,7 +38,10 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; +import android.content.ComponentName; import android.content.Context; +import android.content.IIntentReceiver; +import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; @@ -58,6 +65,7 @@ import android.graphics.drawable.LayerDrawable; import android.os.Binder; import android.os.Bundle; import android.os.Environment; +import android.os.IBinder; import android.os.ParcelableException; import android.os.Process; import android.os.SELinux; @@ -71,6 +79,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo; +import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; @@ -185,6 +194,113 @@ public class PackageArchiver { }); } + /** + * Starts unarchival for the package corresponding to the startActivity intent. Note that this + * will work only if the caller is the default/Home Launcher or if activity is started via Shell + * identity. + */ + @NonNull + public int requestUnarchiveOnActivityStart(@Nullable Intent intent, + @Nullable String callerPackageName, int userId, int callingUid) { + String packageName = getPackageNameFromIntent(intent); + if (packageName == null) { + Slog.e(TAG, "packageName cannot be null for unarchival!"); + return START_CLASS_NOT_FOUND; + } + if (callerPackageName == null) { + Slog.e(TAG, "callerPackageName cannot be null for unarchival!"); + return START_CLASS_NOT_FOUND; + } + if (!isCallingPackageValid(callerPackageName, callingUid, userId)) { + // Return early as the calling UID does not match caller package's UID. + return START_CLASS_NOT_FOUND; + } + String currentLauncherPackageName = getCurrentLauncherPackageName(userId); + if ((currentLauncherPackageName == null || !callerPackageName.equals( + currentLauncherPackageName)) && callingUid != Process.SHELL_UID) { + // TODO(b/311619990): Remove dependency on SHELL_UID for testing + Slog.e(TAG, TextUtils.formatSimple( + "callerPackageName: %s does not qualify for archival of package: " + "%s!", + callerPackageName, packageName)); + return START_PERMISSION_DENIED; + } + // TODO(b/302114464): Handle edge cases & also divert to a dialog based on + // permissions + compat options + Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName)); + try { + final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { + @Override + public void send(int code, Intent intent, String resolvedType, + IBinder allowlistToken, + IIntentReceiver finishedReceiver, String requiredPermission, + Bundle options) { + // TODO(b/302114464): Handle intent sender status codes + } + }; + + requestUnarchive(packageName, callerPackageName, + new IntentSender((IIntentSender) mLocalSender), UserHandle.of(userId)); + } catch (Throwable t) { + Slog.e(TAG, TextUtils.formatSimple( + "Unexpected error occurred while unarchiving package %s: %s.", packageName, + t.getLocalizedMessage())); + return START_ABORTED; + } + return START_SUCCESS; + } + + /** + * Returns true if the componentName targeted by the intent corresponds to that of an archived + * app. + */ + public boolean isIntentResolvedToArchivedApp(Intent intent, int userId) { + String packageName = getPackageNameFromIntent(intent); + if (packageName == null || intent.getComponent() == null) { + return false; + } + PackageState packageState = mPm.snapshotComputer().getPackageStateInternal(packageName); + if (packageState == null) { + return false; + } + PackageUserState userState = packageState.getUserStateOrDefault(userId); + if (!PackageArchiver.isArchived(userState)) { + return false; + } + List<ArchiveState.ArchiveActivityInfo> archiveActivityInfoList = + userState.getArchiveState().getActivityInfos(); + for (int i = 0; i < archiveActivityInfoList.size(); i++) { + if (archiveActivityInfoList.get(i) + .getOriginalComponentName().equals(intent.getComponent())) { + return true; + } + } + Slog.e(TAG, TextUtils.formatSimple( + "Package: %s is archived but component to start main activity" + + " cannot be found!", packageName)); + return false; + } + + @Nullable + private String getCurrentLauncherPackageName(int userId) { + ComponentName defaultLauncherComponent = mPm.snapshotComputer().getDefaultHomeActivity( + userId); + if (defaultLauncherComponent != null) { + return defaultLauncherComponent.getPackageName(); + } + return null; + } + + private boolean isCallingPackageValid(String callingPackage, int callingUid, int userId) { + int packageUid; + packageUid = mPm.snapshotComputer().getPackageUid(callingPackage, 0L, userId); + if (packageUid != callingUid) { + Slog.w(TAG, TextUtils.formatSimple("Calling package: %s does not belong to uid: %d", + callingPackage, callingUid)); + return false; + } + return true; + } + /** Creates archived state for the package and user. */ private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId) throws PackageManager.NameNotFoundException { @@ -403,7 +519,7 @@ public class PackageArchiver { Computer snapshot = mPm.snapshotComputer(); int userId = userHandle.getIdentifier(); int binderUid = Binder.getCallingUid(); - if (!PackageManagerServiceUtils.isRootOrShell(binderUid)) { + if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) { verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid); } snapshot.enforceCrossUserPermission(binderUid, userId, true, true, @@ -780,6 +896,20 @@ public class PackageArchiver { return bytesFromBitmap(BitmapFactory.decodeFile(path.toString())); } + @Nullable + private static String getPackageNameFromIntent(@Nullable Intent intent) { + if (intent == null) { + return null; + } + if (intent.getPackage() != null) { + return intent.getPackage(); + } + if (intent.getComponent() != null) { + return intent.getComponent().getPackageName(); + } + return null; + } + /** * Creates serializable archived activities from existing ArchiveState. */ diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index c9663fc30ef5..882e05dd778b 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1683,21 +1683,24 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements archivedPackageParcel); // Create and commit install archived session. - PackageInstallerSession session = null; - try { - var sessionId = createSessionInternal(params, installerPackageName, - null /*installerAttributionTag*/, Binder.getCallingUid(), userId); - session = openSessionInternal(sessionId); - session.addFile(LOCATION_DATA_APP, "base", 0 /*lengthBytes*/, metadata.toByteArray(), - null /*signature*/); - session.commit(statusReceiver, false /*forTransfer*/); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); - } finally { - if (session != null) { - session.close(); + // Session belongs to the system_server and would not appear anywhere in the Public APIs. + Binder.withCleanCallingIdentity(() -> { + PackageInstallerSession session = null; + try { + var sessionId = createSessionInternal(params, installerPackageName, null + /*installerAttributionTag*/, Binder.getCallingUid(), userId); + session = openSessionInternal(sessionId); + session.addFile(LOCATION_DATA_APP, "base", 0 /*lengthBytes*/, + metadata.toByteArray(), null /*signature*/); + session.commit(statusReceiver, false /*forTransfer*/); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } finally { + if (session != null) { + session.close(); + } } - } + }); } // TODO(b/307299702) Implement error dialog and propagate userActionIntent. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ec3823f365b6..233bf4f9394c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -181,6 +181,8 @@ import com.android.internal.app.ResolverActivity; import com.android.internal.content.F2fsUtils; import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.om.OverlayConfig; +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.telephony.CarrierAppUtils; @@ -220,9 +222,7 @@ import com.android.server.pm.dex.DynamicCodeLogger; import com.android.server.pm.local.PackageManagerLocalImpl; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.PackageParser2; -import com.android.server.pm.parsing.pkg.AndroidPackageInternal; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.LegacyPermissionManagerInternal; import com.android.server.pm.permission.LegacyPermissionManagerService; import com.android.server.pm.permission.LegacyPermissionSettings; @@ -5216,6 +5216,18 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override + public String getSuspendingPackage(String packageName, int userId) { + final int callingUid = Binder.getCallingUid(); + final Computer snapshot = snapshot(); + // This will do visibility checks as well. + if (!snapshot.isPackageSuspendedForUser(packageName, userId)) { + return null; + } + return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId, + callingUid); + } + + @Override public @NonNull ParceledListSlice<FeatureInfo> getSystemAvailableFeatures() { // allow instant applications ArrayList<FeatureInfo> res; @@ -6976,6 +6988,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override + public PackageArchiver getPackageArchiver() { + return mInstallerService.mPackageArchiver; + } + + @Override public void sendPackageRestartedBroadcast(@NonNull String packageName, int uid, @Intent.Flags int flags) { final int userId = UserHandle.getUserId(uid); diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java index 434a62da88c9..15d2fdc35f2b 100644 --- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java +++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java @@ -41,10 +41,10 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.InstallLocationUtils; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.pm.parsing.PackageParser2; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.rollback.RollbackManagerInternal; import java.io.File; diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index b50d0a07aa3a..293b87317ced 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -44,9 +44,9 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; -import com.android.server.pm.parsing.pkg.AndroidPackageInternal; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.permission.LegacyPermissionState; @@ -1732,7 +1732,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal time = 1700251133016L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java", - inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\npublic com.android.server.pm.PackageSetting setScannedAsStoppedSystemApp(boolean)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\npublic @java.lang.Override boolean isScannedAsStoppedSystemApp()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int SCANNED_AS_STOPPED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") + inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.internal.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\npublic com.android.server.pm.PackageSetting setScannedAsStoppedSystemApp(boolean)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\npublic @java.lang.Override boolean isScannedAsStoppedSystemApp()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int SCANNED_AS_STOPPED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java index 56258844a3fe..1089ac943802 100644 --- a/services/core/java/com/android/server/pm/ParallelPackageParser.java +++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java @@ -22,9 +22,9 @@ import android.os.Process; import android.os.Trace; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.util.ConcurrentUtils; import com.android.server.pm.parsing.PackageParser2; -import com.android.server.pm.parsing.pkg.ParsedPackage; import java.io.File; import java.util.concurrent.ArrayBlockingQueue; diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 5312ae6ca84c..bb0017c80d6d 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -30,7 +30,7 @@ import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; -import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedLongSparseArray; diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 639d6d78f953..80f69a4897c2 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -395,11 +395,13 @@ final class RemovePackageHelper { mPm.mSettings.removeRenamedPackageLPw(deletedPs.getRealName()); } if (changedUsers.size() > 0) { - final PreferredActivityHelper preferredActivityHelper = - new PreferredActivityHelper(mPm, mBroadcastHelper); - preferredActivityHelper.updateDefaultHomeNotLocked(mPm.snapshotComputer(), - changedUsers); - mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL); + mPm.mInjector.getBackgroundHandler().post(() -> { + final PreferredActivityHelper preferredActivityHelper = + new PreferredActivityHelper(mPm, mBroadcastHelper); + preferredActivityHelper.updateDefaultHomeNotLocked(mPm.snapshotComputer(), + changedUsers); + mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL); + }); } } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate && outInfo.mRemovedUsers != null && !outInfo.mIsExternal) { diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 22ee963cf9a2..53b84e66840b 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -73,6 +73,7 @@ import android.util.jar.StrictJarFile; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.pm.pkg.component.ParsedProcess; @@ -83,7 +84,6 @@ import com.android.server.SystemConfig; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.library.PackageBackwardCompatibility; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.component.ComponentMutateUtils; diff --git a/services/core/java/com/android/server/pm/ScanRequest.java b/services/core/java/com/android/server/pm/ScanRequest.java index e66a72f0fccb..37cf30bd63fe 100644 --- a/services/core/java/com/android/server/pm/ScanRequest.java +++ b/services/core/java/com/android/server/pm/ScanRequest.java @@ -21,7 +21,7 @@ import android.annotation.Nullable; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index 585e2e48fb85..d0fdfa9bc775 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -46,11 +46,11 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.compat.PlatformCompat; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.utils.Snappable; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 85563172cf05..f90bf4b47644 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1957,6 +1957,19 @@ public class UserManagerService extends IUserManager.Stub { return userTypeDetails.getStatusBarIcon(); } + @Override + public @StringRes int getProfileLabelResId(@UserIdInt int userId) { + checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, + "getProfileLabelResId"); + final UserInfo userInfo = getUserInfoNoChecks(userId); + final UserTypeDetails userTypeDetails = getUserTypeDetails(userInfo); + if (userInfo == null || userTypeDetails == null) { + return Resources.ID_NULL; + } + final int userIndex = userInfo.profileBadge; + return userTypeDetails.getLabel(userIndex); + } + public boolean isProfile(@UserIdInt int userId) { checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile"); return isProfileUnchecked(userId); diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java index 7bdcd685a2e9..56c400a0caf8 100644 --- a/services/core/java/com/android/server/pm/UserTypeDetails.java +++ b/services/core/java/com/android/server/pm/UserTypeDetails.java @@ -54,8 +54,15 @@ public final class UserTypeDetails { /** Whether users of this type can be created. */ private final boolean mEnabled; - // TODO(b/142482943): Currently unused and not set. Hook this up. - private final int mLabel; + /** + * Resource IDs ({@link StringRes}) of the user's labels. This might be used to label a + * user/profile in tabbed views, etc. + * The values are resource IDs referring to the strings not the strings themselves. + * + * <p>This is an array because, in general, there may be multiple users of the same user type. + * In this case, the user is indexed according to its {@link UserInfo#profileBadge}. + */ + private final @Nullable int[] mLabels; /** * Maximum number of this user type allowed on the device. @@ -160,8 +167,8 @@ public final class UserTypeDetails { private final @NonNull UserProperties mDefaultUserProperties; private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed, - @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label, - int maxAllowedPerParent, + @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, + @Nullable int[] labels, int maxAllowedPerParent, int iconBadge, int badgePlain, int badgeNoBackground, int statusBarIcon, @Nullable int[] badgeLabels, @Nullable int[] badgeColors, @@ -181,12 +188,11 @@ public final class UserTypeDetails { this.mDefaultSystemSettings = defaultSystemSettings; this.mDefaultSecureSettings = defaultSecureSettings; this.mDefaultCrossProfileIntentFilters = defaultCrossProfileIntentFilters; - this.mIconBadge = iconBadge; this.mBadgePlain = badgePlain; this.mBadgeNoBackground = badgeNoBackground; this.mStatusBarIcon = statusBarIcon; - this.mLabel = label; + this.mLabels = labels; this.mBadgeLabels = badgeLabels; this.mBadgeColors = badgeColors; this.mDarkThemeBadgeColors = darkThemeBadgeColors; @@ -234,9 +240,16 @@ public final class UserTypeDetails { return mDefaultUserInfoPropertyFlags | mBaseType; } - // TODO(b/142482943) Hook this up; it is currently unused. - public int getLabel() { - return mLabel; + /** + * Returns the resource ID corresponding to the badgeIndexth label name where the badgeIndex is + * expected to be the {@link UserInfo#profileBadge} of the user. If badgeIndex exceeds the + * number of labels, returns the label for the highest index. + */ + public @StringRes int getLabel(int badgeIndex) { + if (mLabels == null || mLabels.length == 0 || badgeIndex < 0) { + return Resources.ID_NULL; + } + return mLabels[Math.min(badgeIndex, mLabels.length - 1)]; } /** Returns whether users of this user type should be badged. */ @@ -358,7 +371,6 @@ public final class UserTypeDetails { pw.print(prefix); pw.print("mMaxAllowedPerParent: "); pw.println(mMaxAllowedPerParent); pw.print(prefix); pw.print("mDefaultUserInfoFlags: "); pw.println(UserInfo.flagsToString(mDefaultUserInfoPropertyFlags)); - pw.print(prefix); pw.print("mLabel: "); pw.println(mLabel); mDefaultUserProperties.println(pw, prefix); final String restrictionsPrefix = prefix + " "; @@ -392,6 +404,8 @@ public final class UserTypeDetails { pw.println(mBadgeColors != null ? mBadgeColors.length : "0(null)"); pw.print(prefix); pw.print("mDarkThemeBadgeColors.length: "); pw.println(mDarkThemeBadgeColors != null ? mDarkThemeBadgeColors.length : "0(null)"); + pw.print(prefix); pw.print("mLabels.length: "); + pw.println(mLabels != null ? mLabels.length : "0(null)"); } /** Builder for a {@link UserTypeDetails}; see that class for documentation. */ @@ -408,7 +422,7 @@ public final class UserTypeDetails { private @Nullable List<DefaultCrossProfileIntentFilter> mDefaultCrossProfileIntentFilters = null; private int mEnabled = 1; - private int mLabel = Resources.ID_NULL; + private @Nullable int[] mLabels = null; private @Nullable int[] mBadgeLabels = null; private @Nullable int[] mBadgeColors = null; private @Nullable int[] mDarkThemeBadgeColors = null; @@ -488,8 +502,8 @@ public final class UserTypeDetails { return this; } - public Builder setLabel(int label) { - mLabel = label; + public Builder setLabels(@StringRes int ... labels) { + mLabels = labels; return this; } @@ -562,7 +576,7 @@ public final class UserTypeDetails { mMaxAllowed, mBaseType, mDefaultUserInfoPropertyFlags, - mLabel, + mLabels, mMaxAllowedPerParent, mIconBadge, mBadgePlain, diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 7da76c18216e..4ef8cb780734 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -128,7 +128,7 @@ public final class UserTypeFactory { .setName(USER_TYPE_PROFILE_CLONE) .setBaseType(FLAG_PROFILE) .setMaxAllowedPerParent(1) - .setLabel(0) + .setLabels(R.string.profile_label_clone) .setIconBadge(com.android.internal.R.drawable.ic_clone_icon_badge) .setBadgePlain(com.android.internal.R.drawable.ic_clone_badge) // Clone doesn't use BadgeNoBackground, so just set to BadgePlain as a placeholder. @@ -154,6 +154,10 @@ public final class UserTypeFactory { UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM) .setCrossProfileIntentResolutionStrategy(UserProperties .CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING) + .setShowInQuietMode( + UserProperties.SHOW_IN_QUIET_MODE_DEFAULT) + .setShowInSharingSurfaces( + UserProperties.SHOW_IN_SHARING_SURFACES_WITH_PARENT) .setMediaSharedWithParent(true) .setCredentialShareableWithParent(true) .setDeleteAppWithParent(true)); @@ -169,7 +173,10 @@ public final class UserTypeFactory { .setBaseType(FLAG_PROFILE) .setDefaultUserInfoPropertyFlags(FLAG_MANAGED_PROFILE) .setMaxAllowedPerParent(1) - .setLabel(0) + .setLabels( + R.string.profile_label_work, + R.string.profile_label_work_2, + R.string.profile_label_work_3) .setIconBadge(com.android.internal.R.drawable.ic_corp_icon_badge_case) .setBadgePlain(com.android.internal.R.drawable.ic_corp_badge_case) .setBadgeNoBackground(com.android.internal.R.drawable.ic_corp_badge_no_background) @@ -193,6 +200,10 @@ public final class UserTypeFactory { .setStartWithParent(true) .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE) + .setShowInQuietMode( + UserProperties.SHOW_IN_QUIET_MODE_PAUSED) + .setShowInSharingSurfaces( + UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) .setAuthAlwaysRequiredToDisableQuietMode(false) .setCredentialShareableWithParent(true)); } @@ -209,7 +220,10 @@ public final class UserTypeFactory { .setName(USER_TYPE_PROFILE_TEST) .setBaseType(FLAG_PROFILE) .setMaxAllowedPerParent(2) - .setLabel(0) + .setLabels( + R.string.profile_label_test, + R.string.profile_label_test, + R.string.profile_label_test) .setIconBadge(com.android.internal.R.drawable.ic_test_icon_badge_experiment) .setBadgePlain(com.android.internal.R.drawable.ic_test_badge_experiment) .setBadgeNoBackground(com.android.internal.R.drawable.ic_test_badge_no_background) @@ -240,7 +254,7 @@ public final class UserTypeFactory { .setBaseType(FLAG_PROFILE) .setMaxAllowed(1) .setEnabled(UserManager.isCommunalProfileEnabled() ? 1 : 0) - .setLabel(0) + .setLabels(R.string.profile_label_communal) .setIconBadge(com.android.internal.R.drawable.ic_test_icon_badge_experiment) .setBadgePlain(com.android.internal.R.drawable.ic_test_badge_experiment) .setBadgeNoBackground(com.android.internal.R.drawable.ic_test_badge_no_background) @@ -276,7 +290,7 @@ public final class UserTypeFactory { .setName(USER_TYPE_PROFILE_PRIVATE) .setBaseType(FLAG_PROFILE) .setMaxAllowedPerParent(1) - .setLabel(0) + .setLabels(R.string.profile_label_private) .setIconBadge(com.android.internal.R.drawable.ic_private_profile_icon_badge) .setBadgePlain(com.android.internal.R.drawable.ic_private_profile_badge) // Private Profile doesn't use BadgeNoBackground, so just set to BadgePlain @@ -298,7 +312,10 @@ public final class UserTypeFactory { .setMediaSharedWithParent(false) .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE) - .setHideInSettingsInQuietMode(true) + .setShowInQuietMode( + UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) + .setShowInSharingSurfaces( + UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) .setCrossProfileIntentFilterAccessControl( UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM) .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)); diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java index 2ab7db47f8e5..459e2cf7f5c0 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java +++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java @@ -28,9 +28,9 @@ import android.system.StructStat; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.ApexManager; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import libcore.io.IoUtils; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 926e0188c624..7910edcb5959 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -411,6 +411,9 @@ public class PackageInfoUtils { ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]); } ai.isArchived = PackageArchiver.isArchived(state); + if (ai.isArchived) { + ai.nonLocalizedLabel = state.getArchiveState().getActivityInfos().get(0).getTitle(); + } } @Nullable diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java index 3b10c7f2c2a6..1c751e07bbbe 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java +++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java @@ -35,12 +35,12 @@ import android.util.DisplayMetrics; import android.util.Slog; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.util.ArrayUtils; import com.android.server.pm.PackageManagerException; import com.android.server.pm.PackageManagerService; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java index 8b5719ac7712..f4e187f578cb 100644 --- a/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java +++ b/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java @@ -21,7 +21,7 @@ import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_H import android.os.Build; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.pkg.ParsedPackage; /** * Updates a package to ensure that if it targets <= P that the android.hidl.base-V1.0-java diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java index adaa04cb9335..34880a8d4c24 100644 --- a/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java +++ b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java @@ -16,7 +16,7 @@ package com.android.server.pm.parsing.library; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.pkg.ParsedPackage; /** * Updates a package to remove dependency on android.net.ipsec.ike library. diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java index 3b29d1f9b881..97e30207f683 100644 --- a/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java +++ b/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java @@ -29,8 +29,8 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; /** diff --git a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java index 041b77b88789..0c38c6006266 100644 --- a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java +++ b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java @@ -19,9 +19,9 @@ package com.android.server.pm.parsing.library; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.modules.utils.build.UnboundedSdkLevel; import com.android.server.SystemConfig; -import com.android.server.pm.parsing.pkg.ParsedPackage; /** * Updates packages to add or remove dependencies on shared libraries as per attributes diff --git a/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java b/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java index b47a768e61b7..1c6c1fba1b93 100644 --- a/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java +++ b/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java @@ -16,7 +16,7 @@ package com.android.server.pm.parsing.library; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.pkg.ParsedPackage; /** * Updates a package to remove dependency on com.google.android.maps library. diff --git a/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java b/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java index ac65c8c9f338..b5589818bf67 100644 --- a/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java +++ b/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java @@ -20,7 +20,7 @@ import static com.android.server.pm.parsing.library.SharedLibraryNames.ORG_APACH import android.os.Build; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; /** diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java index 3da71414f8fc..fe9cd0eec4a6 100644 --- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java +++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java @@ -24,9 +24,9 @@ import static com.android.server.pm.parsing.library.SharedLibraryNames.ORG_APACH import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.SystemConfig; -import com.android.server.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.pkg.parsing.ParsingPackage; import java.util.ArrayList; import java.util.List; diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java b/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java index a9c22d9e6bb9..a5af0059cdf3 100644 --- a/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java +++ b/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java @@ -19,8 +19,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.util.ArrayUtils; -import com.android.server.pm.parsing.pkg.ParsedPackage; import java.util.ArrayList; import java.util.List; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index 0eb2bbde5886..61be6e1036f2 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java @@ -29,17 +29,18 @@ import android.content.pm.parsing.result.ParseTypeImpl; import android.os.incremental.IncrementalManager; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.pm.parsing.pkg.AndroidPackageHidden; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.parsing.ParsingPackageHidden; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.PackageManagerException; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.parsing.ParsingPackageHidden; import java.io.IOException; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java index c8ac6982071b..85d95eab2958 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java @@ -50,6 +50,10 @@ import android.util.SparseIntArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.AndroidPackageHidden; +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal; +import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.AndroidPackageSplitImpl; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.pm.pkg.component.ParsedAttribution; @@ -63,6 +67,8 @@ import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageHidden; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; @@ -71,7 +77,6 @@ import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.AndroidPackageSplit; -import com.android.server.pm.pkg.AndroidPackageSplitImpl; import com.android.server.pm.pkg.SELinuxUtil; import com.android.server.pm.pkg.component.ComponentMutateUtils; import com.android.server.pm.pkg.component.ParsedActivityImpl; @@ -84,8 +89,6 @@ import com.android.server.pm.pkg.component.ParsedProcessImpl; import com.android.server.pm.pkg.component.ParsedProviderImpl; import com.android.server.pm.pkg.component.ParsedServiceImpl; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; -import com.android.server.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.pkg.parsing.ParsingPackageHidden; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 6f6bb45bad50..f7f76aaaee16 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -807,7 +807,7 @@ final class DefaultPermissionGrantPolicy { getDefaultSystemHandlerActivityPackage(pm, SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId), userId, MICROPHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, - NOTIFICATION_PERMISSIONS); + NOTIFICATION_PERMISSIONS, PHONE_PERMISSIONS); } // Voice recognition diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java index fc74a195eb49..c73728393016 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java @@ -22,9 +22,9 @@ import android.annotation.UserIdInt; import android.content.pm.SigningDetails; import android.util.SparseArray; +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal; import com.android.server.pm.InstallSource; import com.android.server.pm.PackageKeySetData; -import com.android.server.pm.parsing.pkg.AndroidPackageInternal; import com.android.server.pm.permission.LegacyPermissionState; import java.util.UUID; diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java index 041edaa98e63..019ca1315af8 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java @@ -32,9 +32,9 @@ import android.text.TextUtils; import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateUtils; -import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java b/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java index 14976848fe61..dd54cfca6518 100644 --- a/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java +++ b/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java @@ -26,8 +26,8 @@ import android.os.Build; import android.util.ArraySet; import com.android.internal.R; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.SystemConfig; -import com.android.server.pm.pkg.parsing.ParsingPackage; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java index 5709cbb09f93..64985bdfd54f 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java @@ -49,8 +49,8 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.util.ArrayUtils; -import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java index c6b9b1a166b6..9322cf0e90f6 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java @@ -31,7 +31,7 @@ import android.text.TextUtils; import android.util.TypedValue; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java index 9792a91fb699..a7116949b911 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java @@ -27,7 +27,7 @@ import android.content.res.XmlResourceParser; import com.android.internal.R; import com.android.internal.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java index 5e67bbf4ab0b..e5e214d2292b 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java @@ -32,7 +32,7 @@ import android.util.TypedValue; import com.android.internal.R; import com.android.internal.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java index 6c22f825bab9..8268f0fdfa3e 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java @@ -33,7 +33,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java index 0f2b49b8541c..4b45d3742a2a 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java @@ -33,7 +33,7 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.pm.pkg.component.ParsedPermissionGroup; -import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java index 766fb90cbfa0..a84954950f44 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java @@ -28,9 +28,9 @@ import android.util.ArraySet; import com.android.internal.R; import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.util.CollectionUtils; import com.android.internal.util.XmlUtils; -import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java index b66db4f9ced4..0b28a1214072 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java @@ -35,7 +35,7 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java index 1b421841f166..171ef594f6fd 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java @@ -33,7 +33,7 @@ import android.os.Build; import com.android.internal.R; import com.android.internal.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index e4594c58f40f..722350a0d7fb 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -89,6 +89,7 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.R; import com.android.internal.os.ClassLoaderFactory; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.pm.pkg.component.ParsedAttribution; @@ -102,11 +103,11 @@ import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import com.android.server.pm.SharedUidMigration; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.component.ComponentMutateUtils; import com.android.server.pm.pkg.component.ComponentParseUtils; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java index 2cfffb3b185d..1d159554e8a7 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java @@ -31,6 +31,7 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.util.Parcelling; import com.android.internal.util.XmlUtils; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 986735f5f2ee..73c422490330 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3537,7 +3537,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness); Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION + | Intent.FLAG_ACTIVITY_NO_USER_ACTION); intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true); startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); logKeyboardSystemsEvent(event, KeyboardLogEvent.getBrightnessEvent(keyCode)); diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java index aadd03b25428..894226cf32c9 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java @@ -33,6 +33,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -265,4 +266,11 @@ class AggregatedPowerStats { ipw.decreaseIndent(); } } + + @Override + public String toString() { + StringWriter sw = new StringWriter(); + dump(new IndentingPrintWriter(sw)); + return sw.toString(); + } } diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java index f9d57e4c9042..a8eda3ca6a47 100644 --- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java @@ -44,11 +44,8 @@ import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.power.EnergyConsumerStats; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; -import libcore.util.EmptyArray; - import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -128,9 +125,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat private boolean mUseLatestStates = true; @GuardedBy("this") - private final IntArray mUidsToRemove = new IntArray(); - - @GuardedBy("this") private Future<?> mWakelockChangesUpdate; @GuardedBy("this") @@ -260,7 +254,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat @Override public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) { - mUidsToRemove.add(uid); return scheduleSyncLocked("remove-uid", UPDATE_CPU); } @@ -459,7 +452,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat // Capture a snapshot of the state we are meant to process. final int updateFlags; final String reason; - final int[] uidsToRemove; final boolean onBattery; final boolean onBatteryScreenOff; final int screenState; @@ -468,7 +460,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat synchronized (BatteryExternalStatsWorker.this) { updateFlags = mUpdateFlags; reason = mCurrentReason; - uidsToRemove = mUidsToRemove.size() > 0 ? mUidsToRemove.toArray() : EmptyArray.INT; onBattery = mOnBattery; onBatteryScreenOff = mOnBatteryScreenOff; screenState = mScreenState; @@ -476,7 +467,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat useLatestStates = mUseLatestStates; mUpdateFlags = 0; mCurrentReason = null; - mUidsToRemove.clear(); mCurrentFuture = null; mUseLatestStates = true; if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) { @@ -512,12 +502,6 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat // Clean up any UIDs if necessary. synchronized (mStats) { - for (int uid : uidsToRemove) { - FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, -1, uid, - FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED); - mStats.maybeRemoveIsolatedUidLocked(uid, SystemClock.elapsedRealtime(), - SystemClock.uptimeMillis()); - } mStats.clearPendingRemovedUidsLocked(); } } catch (Exception e) { diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java new file mode 100644 index 000000000000..ad146afe16e7 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java @@ -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.server.power.stats; + +import android.os.BatteryStats; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; + +public class BatteryStatsDumpHelperImpl implements BatteryStats.BatteryStatsDumpHelper { + private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; + + public BatteryStatsDumpHelperImpl(BatteryUsageStatsProvider batteryUsageStatsProvider) { + mBatteryUsageStatsProvider = batteryUsageStatsProvider; + } + + @Override + public BatteryUsageStats getBatteryUsageStats(BatteryStats batteryStats, boolean detailed) { + BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0); + if (detailed) { + builder.includePowerModels().includeProcessStateData().includeVirtualUids(); + } + return mBatteryUsageStatsProvider.getBatteryUsageStats((BatteryStatsImpl) batteryStats, + builder.build()); + } +} diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index eea13f179b9e..0491c14e52bd 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -185,7 +185,7 @@ public class BatteryStatsImpl extends BatteryStats { // TODO: remove "tcp" from network methods, since we measure total stats. // Current on-disk Parcel version. Must be updated when the format of the parcelable changes - public static final int VERSION = 213; + public static final int VERSION = 214; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -220,6 +220,8 @@ public class BatteryStatsImpl extends BatteryStats { public static final int RESET_REASON_FULL_CHARGE = 3; public static final int RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE = 4; public static final int RESET_REASON_PLUGGED_IN_FOR_LONG_DURATION = 5; + @NonNull + private final MonotonicClock mMonotonicClock; protected Clock mClock; @@ -393,19 +395,9 @@ public class BatteryStatsImpl extends BatteryStats { } } - /** - * Listener for the battery stats reset. - */ - public interface BatteryResetListener { - - /** - * Callback invoked immediately prior to resetting battery stats. - * @param resetReason One of the RESET_REASON_* constants. - */ - void prepareForBatteryStatsReset(int resetReason); - } - - private BatteryResetListener mBatteryResetListener; + private boolean mSaveBatteryUsageStatsOnReset; + private BatteryUsageStatsProvider mBatteryUsageStatsProvider; + private PowerStatsStore mPowerStatsStore; public interface BatteryCallback { public void batteryNeedsCpuUpdate(); @@ -787,13 +779,10 @@ public class BatteryStatsImpl extends BatteryStats { private BatteryCallback mCallback; /** - * Mapping isolated uids to the actual owning app uid. - */ - private final SparseIntArray mIsolatedUids = new SparseIntArray(); - /** - * Internal reference count of isolated uids. + * Mapping child uids to their parent uid. */ - private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray(); + @VisibleForTesting + protected final PowerStatsUidResolver mPowerStatsUidResolver; /** * The statistics we have collected organized by uids. @@ -874,6 +863,8 @@ public class BatteryStatsImpl extends BatteryStats { long mUptimeStartUs; long mRealtimeUs; long mRealtimeStartUs; + long mMonotonicStartTime; + long mMonotonicEndTime = MonotonicClock.UNDEFINED; int mWakeLockNesting; boolean mWakeLockImportant; @@ -1724,25 +1715,26 @@ public class BatteryStatsImpl extends BatteryStats { } @VisibleForTesting - public BatteryStatsImpl(Clock clock, File historyDirectory) { + public BatteryStatsImpl(Clock clock, File historyDirectory, @NonNull Handler handler, + @NonNull PowerStatsUidResolver powerStatsUidResolver) { init(clock); mBatteryStatsConfig = new BatteryStatsConfig.Builder().build(); - mHandler = null; + mHandler = handler; + mPowerStatsUidResolver = powerStatsUidResolver; mConstants = new Constants(mHandler); mStartClockTimeMs = clock.currentTimeMillis(); mDailyFile = null; + mMonotonicClock = new MonotonicClock(0, mClock); if (historyDirectory == null) { mCheckinFile = null; mStatsFile = null; mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, - new MonotonicClock(0, mClock)); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock); } else { mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin")); mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin")); mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, - new MonotonicClock(0, mClock)); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock); } mPlatformIdleStateCallback = null; mEnergyConsumerRetriever = null; @@ -4278,92 +4270,51 @@ public class BatteryStatsImpl extends BatteryStats { } } - @GuardedBy("this") - public void addIsolatedUidLocked(int isolatedUid, int appUid) { - addIsolatedUidLocked(isolatedUid, appUid, - mClock.elapsedRealtime(), mClock.uptimeMillis()); + private void onIsolatedUidAdded(int isolatedUid, int parentUid) { + long realtime = mClock.elapsedRealtime(); + long uptime = mClock.uptimeMillis(); + synchronized (this) { + getUidStatsLocked(parentUid, realtime, uptime).addIsolatedUid(isolatedUid); + } } - @GuardedBy("this") - @SuppressWarnings("GuardedBy") // errorprone false positive on u.addIsolatedUid - public void addIsolatedUidLocked(int isolatedUid, int appUid, - long elapsedRealtimeMs, long uptimeMs) { - mIsolatedUids.put(isolatedUid, appUid); - mIsolatedUidRefCounts.put(isolatedUid, 1); - final Uid u = getUidStatsLocked(appUid, elapsedRealtimeMs, uptimeMs); - u.addIsolatedUid(isolatedUid); + private void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) { + long realtime = mClock.elapsedRealtime(); + mPowerStatsUidResolver.retainIsolatedUid(isolatedUid); + synchronized (this) { + mPendingRemovedUids.add(new UidToRemove(isolatedUid, realtime)); + } + if (mExternalSync != null) { + mExternalSync.scheduleCpuSyncDueToRemovedUid(isolatedUid); + } } - /** - * Schedules a read of the latest cpu times before removing the isolated UID. - * @see #removeIsolatedUidLocked(int, int, int) - */ - public void scheduleRemoveIsolatedUidLocked(int isolatedUid, int appUid) { - int curUid = mIsolatedUids.get(isolatedUid, -1); - if (curUid == appUid) { - if (mExternalSync != null) { - mExternalSync.scheduleCpuSyncDueToRemovedUid(isolatedUid); - } + private void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) { + long realtime = mClock.elapsedRealtime(); + long uptime = mClock.uptimeMillis(); + synchronized (this) { + getUidStatsLocked(parentUid, realtime, uptime).removeIsolatedUid(isolatedUid); } } /** * Isolated uid should only be removed after all wakelocks associated with the uid are stopped * and the cpu time-in-state has been read one last time for the uid. - * - * @see #scheduleRemoveIsolatedUidLocked(int, int) - * - * @return true if the isolated uid is actually removed. */ @GuardedBy("this") - public boolean maybeRemoveIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs, - long uptimeMs) { - final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1; - if (refCount > 0) { - // Isolated uid is still being tracked - mIsolatedUidRefCounts.put(isolatedUid, refCount); - return false; - } - - final int idx = mIsolatedUids.indexOfKey(isolatedUid); - if (idx >= 0) { - final int ownerUid = mIsolatedUids.valueAt(idx); - final Uid u = getUidStatsLocked(ownerUid, elapsedRealtimeMs, uptimeMs); - u.removeIsolatedUid(isolatedUid); - mIsolatedUids.removeAt(idx); - mIsolatedUidRefCounts.delete(isolatedUid); - } else { - Slog.w(TAG, "Attempted to remove untracked isolated uid (" + isolatedUid + ")"); - } - mPendingRemovedUids.add(new UidToRemove(isolatedUid, elapsedRealtimeMs)); - - return true; - } - - /** - * Increment the ref count for an isolated uid. - * call #maybeRemoveIsolatedUidLocked to decrement. - */ - public void incrementIsolatedUidRefCount(int uid) { - final int refCount = mIsolatedUidRefCounts.get(uid, 0); - if (refCount <= 0) { - // Uid is not mapped or referenced - Slog.w(TAG, - "Attempted to increment ref counted of untracked isolated uid (" + uid + ")"); - return; - } - mIsolatedUidRefCounts.put(uid, refCount + 1); + public void releaseIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs, long uptimeMs) { + mPowerStatsUidResolver.releaseIsolatedUid(isolatedUid); } private int mapUid(int uid) { if (Process.isSdkSandboxUid(uid)) { return Process.getAppUidForSdkSandboxUid(uid); } - return mapIsolatedUid(uid); + return mPowerStatsUidResolver.mapUid(uid); } private int mapIsolatedUid(int uid) { - return mIsolatedUids.get(/*key=*/uid, /*valueIfKeyNotFound=*/uid); + return mPowerStatsUidResolver.mapUid(uid); } @GuardedBy("this") @@ -4745,7 +4696,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mappedUid != uid) { // Prevent the isolated uid mapping from being removed while the wakelock is // being held. - incrementIsolatedUidRefCount(uid); + mPowerStatsUidResolver.retainIsolatedUid(uid); } if (mOnBatteryScreenOffTimeBase.isRunning()) { // We only update the cpu time when a wake lock is acquired if the screen is off. @@ -4825,7 +4776,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mappedUid != uid) { // Decrement the ref count for the isolated uid and delete the mapping if uneeded. - maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs); + releaseIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs); } } } @@ -4996,7 +4947,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mappedUid != uid) { // Prevent the isolated uid mapping from being removed while the wakelock is // being held. - incrementIsolatedUidRefCount(uid); + mPowerStatsUidResolver.retainIsolatedUid(uid); } } @@ -5048,7 +4999,7 @@ public class BatteryStatsImpl extends BatteryStats { historyName, mappedUid); if (mappedUid != uid) { // Decrement the ref count for the isolated uid and delete the mapping if uneeded. - maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs); + releaseIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs); } } @@ -7642,35 +7593,53 @@ public class BatteryStatsImpl extends BatteryStats { /** * Returns the names of custom power components. */ - @GuardedBy("this") @Override public @NonNull String[] getCustomEnergyConsumerNames() { - if (mEnergyConsumerStatsConfig == null) { - return new String[0]; - } - final String[] names = mEnergyConsumerStatsConfig.getCustomBucketNames(); - for (int i = 0; i < names.length; i++) { - if (TextUtils.isEmpty(names[i])) { - names[i] = "CUSTOM_" + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i; + synchronized (this) { + if (mEnergyConsumerStatsConfig == null) { + return new String[0]; + } + final String[] names = mEnergyConsumerStatsConfig.getCustomBucketNames(); + for (int i = 0; i < names.length; i++) { + if (TextUtils.isEmpty(names[i])) { + names[i] = "CUSTOM_" + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i; + } } + return names; } - return names; } - @GuardedBy("this") - @Override public long getStartClockTime() { - final long currentTimeMs = mClock.currentTimeMillis(); - if ((currentTimeMs > MILLISECONDS_IN_YEAR - && mStartClockTimeMs < (currentTimeMs - MILLISECONDS_IN_YEAR)) + @Override + public long getStartClockTime() { + synchronized (this) { + final long currentTimeMs = mClock.currentTimeMillis(); + if ((currentTimeMs > MILLISECONDS_IN_YEAR + && mStartClockTimeMs < (currentTimeMs - MILLISECONDS_IN_YEAR)) || (mStartClockTimeMs > currentTimeMs)) { - // If the start clock time has changed by more than a year, then presumably - // the previous time was completely bogus. So we are going to figure out a - // new time based on how much time has elapsed since we started counting. - mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(), - currentTimeMs); - adjustStartClockTime(currentTimeMs); + // If the start clock time has changed by more than a year, then presumably + // the previous time was completely bogus. So we are going to figure out a + // new time based on how much time has elapsed since we started counting. + mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(), + currentTimeMs); + adjustStartClockTime(currentTimeMs); + } + return mStartClockTimeMs; } - return mStartClockTimeMs; + } + + /** + * Returns the monotonic time when the BatteryStats session started. + */ + public long getMonotonicStartTime() { + return mMonotonicStartTime; + } + + /** + * Returns the monotonic time when the BatteryStats session ended, or + * {@link MonotonicClock#UNDEFINED} if the session is still ongoing. + */ + public long getMonotonicEndTime() { + return mMonotonicEndTime; } @Override public String getStartPlatformVersion() { @@ -8197,7 +8166,9 @@ public class BatteryStatsImpl extends BatteryStats { return mProportionalSystemServiceUsage; } - @GuardedBy("mBsi") + /** + * Adds isolated UID to the list of children. + */ public void addIsolatedUid(int isolatedUid) { if (mChildUids == null) { mChildUids = new SparseArray<>(); @@ -8207,6 +8178,9 @@ public class BatteryStatsImpl extends BatteryStats { mChildUids.put(isolatedUid, new ChildUid()); } + /** + * Removes isolated UID from the list of children. + */ public void removeIsolatedUid(int isolatedUid) { final int idx = mChildUids == null ? -1 : mChildUids.indexOfKey(isolatedUid); if (idx < 0) { @@ -8239,20 +8213,20 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("mBsi") private void ensureMultiStateCounters(long timestampMs) { - if (mProcStateTimeMs != null) { - return; + if (mProcStateTimeMs == null) { + mProcStateTimeMs = + new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase, + PROC_STATE_TIME_COUNTER_STATE_COUNT, + mBsi.mCpuScalingPolicies.getScalingStepCount(), + timestampMs); + } + if (mProcStateScreenOffTimeMs == null) { + mProcStateScreenOffTimeMs = + new TimeInFreqMultiStateCounter(mBsi.mOnBatteryScreenOffTimeBase, + PROC_STATE_TIME_COUNTER_STATE_COUNT, + mBsi.mCpuScalingPolicies.getScalingStepCount(), + timestampMs); } - - mProcStateTimeMs = - new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase, - PROC_STATE_TIME_COUNTER_STATE_COUNT, - mBsi.mCpuScalingPolicies.getScalingStepCount(), - timestampMs); - mProcStateScreenOffTimeMs = - new TimeInFreqMultiStateCounter(mBsi.mOnBatteryScreenOffTimeBase, - PROC_STATE_TIME_COUNTER_STATE_COUNT, - mBsi.mCpuScalingPolicies.getScalingStepCount(), - timestampMs); } @GuardedBy("mBsi") @@ -10910,15 +10884,18 @@ public class BatteryStatsImpl extends BatteryStats { @NonNull Handler handler, @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb, @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile, - @NonNull CpuScalingPolicies cpuScalingPolicies) { + @NonNull CpuScalingPolicies cpuScalingPolicies, + @NonNull PowerStatsUidResolver powerStatsUidResolver) { init(clock); mBatteryStatsConfig = config; + mMonotonicClock = monotonicClock; mHandler = new MyHandler(handler.getLooper()); mConstants = new Constants(mHandler); mPowerProfile = powerProfile; mCpuScalingPolicies = cpuScalingPolicies; + mPowerStatsUidResolver = powerStatsUidResolver; initPowerProfile(); @@ -10927,17 +10904,17 @@ public class BatteryStatsImpl extends BatteryStats { mCheckinFile = null; mDailyFile = null; mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock); } else { mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin")); mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin")); mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml")); mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES, - mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock); + mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock); } mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile, - () -> mBatteryVoltageMv, mHandler, + mPowerStatsUidResolver, () -> mBatteryVoltageMv, mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu()); mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); @@ -10954,6 +10931,23 @@ public class BatteryStatsImpl extends BatteryStats { mEnergyConsumerRetriever = energyStatsCb; mUserInfoProvider = userInfoProvider; + mPowerStatsUidResolver.addListener(new PowerStatsUidResolver.Listener() { + @Override + public void onIsolatedUidAdded(int isolatedUid, int parentUid) { + BatteryStatsImpl.this.onIsolatedUidAdded(isolatedUid, parentUid); + } + + @Override + public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) { + BatteryStatsImpl.this.onBeforeIsolatedUidRemoved(isolatedUid, parentUid); + } + + @Override + public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) { + BatteryStatsImpl.this.onAfterIsolatedUidRemoved(isolatedUid, parentUid); + } + }); + // Notify statsd that the system is initially not in doze. mDeviceIdleMode = DEVICE_IDLE_MODE_OFF; FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode); @@ -11497,6 +11491,7 @@ public class BatteryStatsImpl extends BatteryStats { mUptimeUs = 0; mRealtimeStartUs = realtimeUs; mUptimeStartUs = uptimeUs; + mMonotonicStartTime = mMonotonicClock.monotonicTime(); } void initDischarge(long elapsedRealtimeUs) { @@ -11517,8 +11512,17 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeCounter.reset(false, elapsedRealtimeUs); } - public void setBatteryResetListener(BatteryResetListener batteryResetListener) { - mBatteryResetListener = batteryResetListener; + /** + * Associates the BatteryStatsImpl object with a BatteryUsageStatsProvider and PowerStatsStore + * to allow for a snapshot of battery usage stats to be taken and stored just before battery + * reset. + */ + public void saveBatteryUsageStatsOnReset( + @NonNull BatteryUsageStatsProvider batteryUsageStatsProvider, + @NonNull PowerStatsStore powerStatsStore) { + mSaveBatteryUsageStatsOnReset = true; + mBatteryUsageStatsProvider = batteryUsageStatsProvider; + mPowerStatsStore = powerStatsStore; } @GuardedBy("this") @@ -11557,9 +11561,7 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") private void resetAllStatsLocked(long uptimeMillis, long elapsedRealtimeMillis, int resetReason) { - if (mBatteryResetListener != null) { - mBatteryResetListener.prepareForBatteryStatsReset(resetReason); - } + saveBatteryUsageStatsOnReset(resetReason); final long uptimeUs = uptimeMillis * 1000; final long elapsedRealtimeUs = elapsedRealtimeMillis * 1000; @@ -11707,6 +11709,31 @@ public class BatteryStatsImpl extends BatteryStats { mHandler.sendEmptyMessage(MSG_REPORT_RESET_STATS); } + private void saveBatteryUsageStatsOnReset(int resetReason) { + if (!mSaveBatteryUsageStatsOnReset + || resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) { + return; + } + + final BatteryUsageStats batteryUsageStats; + synchronized (this) { + batteryUsageStats = mBatteryUsageStatsProvider.getBatteryUsageStats(this, + new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0) + .includePowerModels() + .includeProcessStateData() + .build()); + } + + // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end + // Once that change is made, we will be able to use the BatteryUsageStats' monotonic + // start time + long monotonicStartTime = + mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration(); + mHandler.post(() -> + mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats)); + } + @GuardedBy("this") private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) { for (int i=0; i<HistoryItem.EVENT_COUNT; i++) { @@ -15137,6 +15164,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mKernelSingleUidTimeReader != null) { mKernelSingleUidTimeReader.removeUidsInRange(startUid, endUid); } + mPowerStatsUidResolver.releaseUidsInRange(startUid, endUid); // Treat as one. We don't know how many uids there are in between. mNumUidsRemoved++; } else { @@ -15192,10 +15220,11 @@ public class BatteryStatsImpl extends BatteryStats { mShuttingDown = true; } - @GuardedBy("this") @Override public boolean isProcessStateDataAvailable() { - return trackPerProcStateCpuTimes(); + synchronized (this) { + return trackPerProcStateCpuTimes(); + } } @GuardedBy("this") @@ -15862,6 +15891,8 @@ public class BatteryStatsImpl extends BatteryStats { mUptimeUs = in.readLong(); mRealtimeUs = in.readLong(); mStartClockTimeMs = in.readLong(); + mMonotonicStartTime = in.readLong(); + mMonotonicEndTime = in.readLong(); mStartPlatformVersion = in.readString(); mEndPlatformVersion = in.readString(); mOnBatteryTimeBase.readSummaryFromParcel(in); @@ -16382,6 +16413,8 @@ public class BatteryStatsImpl extends BatteryStats { out.writeLong(computeUptime(nowUptime, STATS_SINCE_CHARGED)); out.writeLong(computeRealtime(nowRealtime, STATS_SINCE_CHARGED)); out.writeLong(mStartClockTimeMs); + out.writeLong(mMonotonicStartTime); + out.writeLong(mMonotonicClock.monotonicTime()); out.writeString(mStartPlatformVersion); out.writeString(mEndPlatformVersion); mOnBatteryTimeBase.writeSummaryToParcel(out, nowUptime, nowRealtime); @@ -16912,7 +16945,8 @@ public class BatteryStatsImpl extends BatteryStats { } @GuardedBy("this") - public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart) { + public void dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart, + BatteryStatsDumpHelper dumpHelper) { if (DEBUG) { pw.println("mOnBatteryTimeBase:"); mOnBatteryTimeBase.dump(pw, " "); @@ -16984,7 +17018,7 @@ public class BatteryStatsImpl extends BatteryStats { pr.println("*** Camera timer:"); mCameraOnTimer.logState(pr, " "); } - super.dump(context, pw, flags, reqUid, histStart); + super.dump(context, pw, flags, reqUid, histStart, dumpHelper); synchronized (this) { pw.print("Per process state tracking available: "); @@ -16998,15 +17032,7 @@ public class BatteryStatsImpl extends BatteryStats { pw.print("UIDs removed since the later of device start or stats reset: "); pw.println(mNumUidsRemoved); - pw.println("Currently mapped isolated uids:"); - final int numIsolatedUids = mIsolatedUids.size(); - for (int i = 0; i < numIsolatedUids; i++) { - final int isolatedUid = mIsolatedUids.keyAt(i); - final int ownerUid = mIsolatedUids.valueAt(i); - final int refCount = mIsolatedUidRefCounts.get(isolatedUid); - pw.println( - " " + isolatedUid + "->" + ownerUid + " (ref count = " + refCount + ")"); - } + mPowerStatsUidResolver.dump(pw); pw.println(); dumpConstantsLocked(pw); @@ -17020,15 +17046,4 @@ public class BatteryStatsImpl extends BatteryStats { dumpEnergyConsumerStatsLocked(pw); } } - - @Override - protected BatteryUsageStats getBatteryUsageStats(Context context, boolean detailed) { - final BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, this); - BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder() - .setMaxStatsAgeMs(0); - if (detailed) { - builder.includePowerModels().includeProcessStateData().includeVirtualUids(); - } - return provider.getBatteryUsageStats(builder.build()); - } } diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index 83d7d72059b4..303c2457165a 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -23,14 +23,12 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; -import android.os.SystemClock; import android.os.UidBatteryConsumer; import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.Clock; import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; @@ -45,27 +43,25 @@ import java.util.List; public class BatteryUsageStatsProvider { private static final String TAG = "BatteryUsageStatsProv"; private final Context mContext; - private final BatteryStats mStats; + private boolean mPowerStatsExporterEnabled; + private final PowerStatsExporter mPowerStatsExporter; private final PowerStatsStore mPowerStatsStore; private final PowerProfile mPowerProfile; private final CpuScalingPolicies mCpuScalingPolicies; + private final Clock mClock; private final Object mLock = new Object(); private List<PowerCalculator> mPowerCalculators; - public BatteryUsageStatsProvider(Context context, BatteryStats stats) { - this(context, stats, null); - } - - @VisibleForTesting - public BatteryUsageStatsProvider(Context context, BatteryStats stats, - PowerStatsStore powerStatsStore) { + public BatteryUsageStatsProvider(Context context, + PowerStatsExporter powerStatsExporter, + PowerProfile powerProfile, CpuScalingPolicies cpuScalingPolicies, + PowerStatsStore powerStatsStore, Clock clock) { mContext = context; - mStats = stats; + mPowerStatsExporter = powerStatsExporter; mPowerStatsStore = powerStatsStore; - mPowerProfile = stats instanceof BatteryStatsImpl - ? ((BatteryStatsImpl) stats).getPowerProfile() - : new PowerProfile(context); - mCpuScalingPolicies = stats.getCpuScalingPolicies(); + mPowerProfile = powerProfile; + mCpuScalingPolicies = cpuScalingPolicies; + mClock = clock; } private List<PowerCalculator> getPowerCalculators() { @@ -75,7 +71,10 @@ public class BatteryUsageStatsProvider { // Power calculators are applied in the order of registration mPowerCalculators.add(new BatteryChargeCalculator()); - mPowerCalculators.add(new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile)); + if (mPowerStatsExporterEnabled) { + mPowerCalculators.add( + new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile)); + } mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile)); mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile)); if (!BatteryStats.checkWifiOnly(mContext)) { @@ -111,27 +110,28 @@ public class BatteryUsageStatsProvider { * Returns true if the last update was too long ago for the tolerances specified * by the supplied queries. */ - public boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries, - long lastUpdateTimeStampMs) { + public static boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries, + long elapsedRealtime, long lastUpdateTimeStampMs) { long allowableStatsAge = Long.MAX_VALUE; for (int i = queries.size() - 1; i >= 0; i--) { BatteryUsageStatsQuery query = queries.get(i); allowableStatsAge = Math.min(allowableStatsAge, query.getMaxStatsAge()); } - return elapsedRealtime() - lastUpdateTimeStampMs > allowableStatsAge; + return elapsedRealtime - lastUpdateTimeStampMs > allowableStatsAge; } /** * Returns snapshots of battery attribution data, one per supplied query. */ - public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { + public List<BatteryUsageStats> getBatteryUsageStats(BatteryStatsImpl stats, + List<BatteryUsageStatsQuery> queries) { ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size()); - synchronized (mStats) { - mStats.prepareForDumpLocked(); - final long currentTimeMillis = currentTimeMillis(); + synchronized (stats) { + stats.prepareForDumpLocked(); + final long currentTimeMillis = mClock.currentTimeMillis(); for (int i = 0; i < queries.size(); i++) { - results.add(getBatteryUsageStats(queries.get(i), currentTimeMillis)); + results.add(getBatteryUsageStats(stats, queries.get(i), currentTimeMillis)); } } return results; @@ -140,60 +140,59 @@ public class BatteryUsageStatsProvider { /** * Returns a snapshot of battery attribution data. */ - @VisibleForTesting - public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) { - synchronized (mStats) { - return getBatteryUsageStats(query, currentTimeMillis()); - } + public BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats, + BatteryUsageStatsQuery query) { + return getBatteryUsageStats(stats, query, mClock.currentTimeMillis()); } - @GuardedBy("mStats") - private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query, - long currentTimeMs) { + private BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats, + BatteryUsageStatsQuery query, long currentTimeMs) { if (query.getToTimestamp() == 0) { - return getCurrentBatteryUsageStats(query, currentTimeMs); + return getCurrentBatteryUsageStats(stats, query, currentTimeMs); } else { - return getAggregatedBatteryUsageStats(query); + return getAggregatedBatteryUsageStats(stats, query); } } - @GuardedBy("mStats") - private BatteryUsageStats getCurrentBatteryUsageStats(BatteryUsageStatsQuery query, - long currentTimeMs) { - final long realtimeUs = elapsedRealtime() * 1000; - final long uptimeUs = uptimeMillis() * 1000; + private BatteryUsageStats getCurrentBatteryUsageStats(BatteryStatsImpl stats, + BatteryUsageStatsQuery query, long currentTimeMs) { + final long realtimeUs = mClock.elapsedRealtime() * 1000; + final long uptimeUs = mClock.uptimeMillis() * 1000; final boolean includePowerModels = (query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; final boolean includeProcessStateData = ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0) - && mStats.isProcessStateDataAvailable(); + && stats.isProcessStateDataAvailable(); final boolean includeVirtualUids = ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0); final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder( - mStats.getCustomEnergyConsumerNames(), includePowerModels, + stats.getCustomEnergyConsumerNames(), includePowerModels, includeProcessStateData, minConsumedPowerThreshold); // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration - // of stats sessions to wall-clock adjustments - batteryUsageStatsBuilder.setStatsStartTimestamp(mStats.getStartClockTime()); + // of batteryUsageStats sessions to wall-clock adjustments + batteryUsageStatsBuilder.setStatsStartTimestamp(stats.getStartClockTime()); batteryUsageStatsBuilder.setStatsEndTimestamp(currentTimeMs); - SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats(); - for (int i = uidStats.size() - 1; i >= 0; i--) { - final BatteryStats.Uid uid = uidStats.valueAt(i); - if (!includeVirtualUids && uid.getUid() == Process.SDK_SANDBOX_VIRTUAL_UID) { - continue; - } + synchronized (stats) { + SparseArray<? extends BatteryStats.Uid> uidStats = stats.getUidStats(); + for (int i = uidStats.size() - 1; i >= 0; i--) { + final BatteryStats.Uid uid = uidStats.valueAt(i); + if (!includeVirtualUids && uid.getUid() == Process.SDK_SANDBOX_VIRTUAL_UID) { + continue; + } - batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid) - .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND, - getProcessBackgroundTimeMs(uid, realtimeUs)) - .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND, - getProcessForegroundTimeMs(uid, realtimeUs)) - .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, - getProcessForegroundServiceTimeMs(uid, realtimeUs)); + batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid) + .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND, + getProcessBackgroundTimeMs(uid, realtimeUs)) + .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND, + getProcessForegroundTimeMs(uid, realtimeUs)) + .setTimeInProcessStateMs( + UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, + getProcessForegroundServiceTimeMs(uid, realtimeUs)); + } } final int[] powerComponents = query.getPowerComponents(); @@ -202,8 +201,8 @@ public class BatteryUsageStatsProvider { PowerCalculator powerCalculator = powerCalculators.get(i); if (powerComponents != null) { boolean include = false; - for (int j = 0; j < powerComponents.length; j++) { - if (powerCalculator.isPowerComponentSupported(powerComponents[j])) { + for (int powerComponent : powerComponents) { + if (powerCalculator.isPowerComponentSupported(powerComponent)) { include = true; break; } @@ -212,26 +211,24 @@ public class BatteryUsageStatsProvider { continue; } } - powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs, - query); + powerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs, query); + } + + if (mPowerStatsExporterEnabled) { + mPowerStatsExporter.exportAggregatedPowerStats(batteryUsageStatsBuilder, + stats.getMonotonicStartTime(), stats.getMonotonicEndTime()); } if ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) { - if (!(mStats instanceof BatteryStatsImpl)) { - throw new UnsupportedOperationException( - "History cannot be included for " + getClass().getName()); - } - - BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats; - batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.copyHistory()); + batteryUsageStatsBuilder.setBatteryHistory(stats.copyHistory()); } - BatteryUsageStats stats = batteryUsageStatsBuilder.build(); + BatteryUsageStats batteryUsageStats = batteryUsageStatsBuilder.build(); if (includeProcessStateData) { - verify(stats); + verify(batteryUsageStats); } - return stats; + return batteryUsageStats; } // STOPSHIP(b/229906525): remove verification before shipping @@ -308,15 +305,16 @@ public class BatteryUsageStatsProvider { / 1000; } - private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) { + private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryStatsImpl stats, + BatteryUsageStatsQuery query) { final boolean includePowerModels = (query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; final boolean includeProcessStateData = ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0) - && mStats.isProcessStateDataAvailable(); + && stats.isProcessStateDataAvailable(); final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); - final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames(); + final String[] customEnergyConsumerNames = stats.getCustomEnergyConsumerNames(); final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( customEnergyConsumerNames, includePowerModels, includeProcessStateData, minConsumedPowerThreshold); @@ -386,27 +384,8 @@ public class BatteryUsageStatsProvider { return builder.build(); } - private long elapsedRealtime() { - if (mStats instanceof BatteryStatsImpl) { - return ((BatteryStatsImpl) mStats).mClock.elapsedRealtime(); - } else { - return SystemClock.elapsedRealtime(); - } - } + public void setPowerStatsExporterEnabled(boolean enabled) { - private long uptimeMillis() { - if (mStats instanceof BatteryStatsImpl) { - return ((BatteryStatsImpl) mStats).mClock.uptimeMillis(); - } else { - return SystemClock.uptimeMillis(); - } - } - - private long currentTimeMillis() { - if (mStats instanceof BatteryStatsImpl) { - return ((BatteryStatsImpl) mStats).mClock.currentTimeMillis(); - } else { - return System.currentTimeMillis(); - } + mPowerStatsExporterEnabled = enabled; } } diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java index f40eef2ed820..ed9414ff53a1 100644 --- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java @@ -64,7 +64,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces private PowerStats.Descriptor mLastUsedDescriptor; // Cached results of parsing of current PowerStats.Descriptor. Only refreshed when // mLastUsedDescriptor changes - private CpuPowerStatsCollector.StatsArrayLayout mStatsLayout; + private CpuPowerStatsCollector.CpuStatsArrayLayout mStatsLayout; // Sequence of steps for power estimation and intermediate results. private PowerEstimationPlan mPlan; @@ -106,7 +106,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces } mLastUsedDescriptor = descriptor; - mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout(); + mStatsLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout(); mStatsLayout.fromExtras(descriptor.extras); mTmpDeviceStatsArray = new long[descriptor.statsArrayLength]; @@ -149,7 +149,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces if (mPlan == null) { mPlan = new PowerEstimationPlan(stats.getConfig()); - if (mStatsLayout.getCpuClusterEnergyConsumerCount() != 0) { + if (mStatsLayout.getEnergyConsumerCount() != 0) { initEnergyConsumerToPowerBracketMaps(); } } @@ -212,7 +212,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces * CL_2: [bracket3, bracket4] */ private void initEnergyConsumerToPowerBracketMaps() { - int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount]; @@ -294,7 +294,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces continue; } - intermediates.uptime += mStatsLayout.getUptime(mTmpDeviceStatsArray); + intermediates.uptime += mStatsLayout.getUsageDuration(mTmpDeviceStatsArray); for (int cluster = 0; cluster < mCpuClusterCount; cluster++) { intermediates.timeByCluster[cluster] += @@ -351,7 +351,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount(); int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); int[] scalingStepToBracketMap = mStatsLayout.getScalingStepToPowerBracketMap(); - int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); List<DeviceStateEstimation> deviceStateEstimations = mPlan.deviceStateEstimations; for (int dse = deviceStateEstimations.size() - 1; dse >= 0; dse--) { DeviceStateEstimation deviceStateEstimation = deviceStateEstimations.get(dse); @@ -392,7 +392,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces private void adjustEstimatesUsingEnergyConsumers( Intermediates intermediates, DeviceStatsIntermediates deviceStatsIntermediates) { - int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); if (energyConsumerCount == 0) { return; } @@ -509,8 +509,8 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces } sb.append(mStatsLayout.getTimeByCluster(stats, cluster)); } - sb.append("] uptime: ").append(mStatsLayout.getUptime(stats)); - int energyConsumerCount = mStatsLayout.getCpuClusterEnergyConsumerCount(); + sb.append("] uptime: ").append(mStatsLayout.getUsageDuration(stats)); + int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); if (energyConsumerCount > 0) { sb.append(" energy: ["); for (int i = 0; i < energyConsumerCount; i++) { diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java index b8e581f32a58..c05407cb6d17 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java @@ -22,6 +22,7 @@ import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; import android.os.Handler; import android.os.PersistableBundle; +import android.os.Process; import android.power.PowerStatsInternal; import android.util.Slog; import android.util.SparseArray; @@ -67,6 +68,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { private final CpuScalingPolicies mCpuScalingPolicies; private final PowerProfile mPowerProfile; private final KernelCpuStatsReader mKernelCpuStatsReader; + private final PowerStatsUidResolver mUidResolver; private final Supplier<PowerStatsInternal> mPowerStatsSupplier; private final IntSupplier mVoltageSupplier; private final int mDefaultCpuPowerBrackets; @@ -81,7 +83,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { private PowerStats.Descriptor mPowerStatsDescriptor; // Reusable instance private PowerStats mCpuPowerStats; - private StatsArrayLayout mLayout; + private CpuStatsArrayLayout mLayout; private long mLastUpdateTimestampNanos; private long mLastUpdateUptimeMillis; private int mLastVoltageMv; @@ -91,55 +93,30 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { * Captures the positions and lengths of sections of the stats array, such as time-in-state, * power usage estimates etc. */ - public static class StatsArrayLayout { + public static class CpuStatsArrayLayout extends StatsArrayLayout { private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION = "dt"; private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT = "dtc"; private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION = "dc"; private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT = "dcc"; - private static final String EXTRA_DEVICE_POWER_POSITION = "dp"; - private static final String EXTRA_DEVICE_UPTIME_POSITION = "du"; - private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de"; - private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec"; private static final String EXTRA_UID_BRACKETS_POSITION = "ub"; private static final String EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET = "us"; - private static final String EXTRA_UID_POWER_POSITION = "up"; - - private static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; - - private int mDeviceStatsArrayLength; - private int mUidStatsArrayLength; private int mDeviceCpuTimeByScalingStepPosition; private int mDeviceCpuTimeByScalingStepCount; private int mDeviceCpuTimeByClusterPosition; private int mDeviceCpuTimeByClusterCount; - private int mDeviceCpuUptimePosition; - private int mDeviceEnergyConsumerPosition; - private int mDeviceEnergyConsumerCount; - private int mDevicePowerEstimatePosition; private int mUidPowerBracketsPosition; private int mUidPowerBracketCount; - private int[][] mEnergyConsumerToPowerBucketMaps; - private int mUidPowerEstimatePosition; private int[] mScalingStepToPowerBracketMap; - public int getDeviceStatsArrayLength() { - return mDeviceStatsArrayLength; - } - - public int getUidStatsArrayLength() { - return mUidStatsArrayLength; - } - /** * Declare that the stats array has a section capturing CPU time per scaling step */ public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) { - mDeviceCpuTimeByScalingStepPosition = mDeviceStatsArrayLength; + mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount); mDeviceCpuTimeByScalingStepCount = scalingStepCount; - mDeviceStatsArrayLength += scalingStepCount; } public int getCpuScalingStepCount() { @@ -166,9 +143,8 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { * Declare that the stats array has a section capturing CPU time in each cluster */ public void addDeviceSectionCpuTimeByCluster(int clusterCount) { + mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount); mDeviceCpuTimeByClusterCount = clusterCount; - mDeviceCpuTimeByClusterPosition = mDeviceStatsArrayLength; - mDeviceStatsArrayLength += clusterCount; } public int getCpuClusterCount() { @@ -192,86 +168,12 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { } /** - * Declare that the stats array has a section capturing CPU uptime - */ - public void addDeviceSectionUptime() { - mDeviceCpuUptimePosition = mDeviceStatsArrayLength++; - } - - /** - * Saves the CPU uptime duration in the corresponding <code>stats</code> element. - */ - public void setUptime(long[] stats, long value) { - stats[mDeviceCpuUptimePosition] = value; - } - - /** - * Extracts the CPU uptime duration from the corresponding <code>stats</code> element. - */ - public long getUptime(long[] stats) { - return stats[mDeviceCpuUptimePosition]; - } - - /** - * Declares that the stats array has a section capturing EnergyConsumer data from - * PowerStatsService. - */ - public void addDeviceSectionEnergyConsumers(int energyConsumerCount) { - mDeviceEnergyConsumerPosition = mDeviceStatsArrayLength; - mDeviceEnergyConsumerCount = energyConsumerCount; - mDeviceStatsArrayLength += energyConsumerCount; - } - - public int getCpuClusterEnergyConsumerCount() { - return mDeviceEnergyConsumerCount; - } - - /** - * Saves the accumulated energy for the specified rail the corresponding - * <code>stats</code> element. - */ - public void setConsumedEnergy(long[] stats, int index, long energy) { - stats[mDeviceEnergyConsumerPosition + index] = energy; - } - - /** - * Extracts the EnergyConsumer data from a device stats array for the specified - * EnergyConsumer. - */ - public long getConsumedEnergy(long[] stats, int index) { - return stats[mDeviceEnergyConsumerPosition + index]; - } - - /** - * Declare that the stats array has a section capturing a power estimate - */ - public void addDeviceSectionPowerEstimate() { - mDevicePowerEstimatePosition = mDeviceStatsArrayLength++; - } - - /** - * Converts the supplied mAh power estimate to a long and saves it in the corresponding - * element of <code>stats</code>. - */ - public void setDevicePowerEstimate(long[] stats, double power) { - stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); - } - - /** - * Extracts the power estimate from a device stats array and converts it to mAh. - */ - public double getDevicePowerEstimate(long[] stats) { - return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; - } - - /** * Declare that the UID stats array has a section capturing CPU time per power bracket. */ public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) { mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap; - mUidPowerBracketsPosition = mUidStatsArrayLength; updatePowerBracketCount(); - mUidStatsArrayLength += mUidPowerBracketCount; + mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount); } private void updatePowerBracketCount() { @@ -306,31 +208,10 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { } /** - * Declare that the UID stats array has a section capturing a power estimate - */ - public void addUidSectionPowerEstimate() { - mUidPowerEstimatePosition = mUidStatsArrayLength++; - } - - /** - * Converts the supplied mAh power estimate to a long and saves it in the corresponding - * element of <code>stats</code>. - */ - public void setUidPowerEstimate(long[] stats, double power) { - stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); - } - - /** - * Extracts the power estimate from a UID stats array and converts it to mAh. - */ - public double getUidPowerEstimate(long[] stats) { - return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; - } - - /** * Copies the elements of the stats array layout into <code>extras</code> */ public void toExtras(PersistableBundle extras) { + super.toExtras(extras); extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION, mDeviceCpuTimeByScalingStepPosition); extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT, @@ -339,22 +220,16 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { mDeviceCpuTimeByClusterPosition); extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT, mDeviceCpuTimeByClusterCount); - extras.putInt(EXTRA_DEVICE_UPTIME_POSITION, mDeviceCpuUptimePosition); - extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION, - mDeviceEnergyConsumerPosition); - extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT, - mDeviceEnergyConsumerCount); - extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition); extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition); - extras.putIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET, + putIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET, mScalingStepToPowerBracketMap); - extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition); } /** * Retrieves elements of the stats array layout from <code>extras</code> */ public void fromExtras(PersistableBundle extras) { + super.fromExtras(extras); mDeviceCpuTimeByScalingStepPosition = extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION); mDeviceCpuTimeByScalingStepCount = @@ -363,43 +238,39 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION); mDeviceCpuTimeByClusterCount = extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT); - mDeviceCpuUptimePosition = extras.getInt(EXTRA_DEVICE_UPTIME_POSITION); - mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION); - mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT); - mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION); mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION); mScalingStepToPowerBracketMap = - extras.getIntArray(EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET); + getIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET); if (mScalingStepToPowerBracketMap == null) { mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount]; } updatePowerBracketCount(); - mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION); } } public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile, - IntSupplier voltageSupplier, Handler handler, long throttlePeriodMs) { - this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(), + PowerStatsUidResolver uidResolver, IntSupplier voltageSupplier, Handler handler, + long throttlePeriodMs) { + this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(), uidResolver, () -> LocalServices.getService(PowerStatsInternal.class), voltageSupplier, throttlePeriodMs, Clock.SYSTEM_CLOCK, DEFAULT_CPU_POWER_BRACKETS, DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER); } public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile, - Handler handler, KernelCpuStatsReader kernelCpuStatsReader, - Supplier<PowerStatsInternal> powerStatsSupplier, + Handler handler, KernelCpuStatsReader kernelCpuStatsReader, + PowerStatsUidResolver uidResolver, Supplier<PowerStatsInternal> powerStatsSupplier, IntSupplier voltageSupplier, long throttlePeriodMs, Clock clock, int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) { super(handler, throttlePeriodMs, clock); mCpuScalingPolicies = cpuScalingPolicies; mPowerProfile = powerProfile; mKernelCpuStatsReader = kernelCpuStatsReader; + mUidResolver = uidResolver; mPowerStatsSupplier = powerStatsSupplier; mVoltageSupplier = voltageSupplier; mDefaultCpuPowerBrackets = defaultCpuPowerBrackets; mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer; - } /** @@ -409,13 +280,13 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { setEnabled(Flags.streamlinedBatteryStats()); } - private void ensureInitialized() { + private boolean ensureInitialized() { if (mIsInitialized) { - return; + return true; } if (!isEnabled()) { - return; + return false; } mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.nativeIsSupportedFeature(); @@ -432,10 +303,10 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { mTempCpuTimeByScalingStep = new long[cpuScalingStepCount]; int[] scalingStepToPowerBracketMap = initPowerBrackets(); - mLayout = new StatsArrayLayout(); + mLayout = new CpuStatsArrayLayout(); mLayout.addDeviceSectionCpuTimeByScalingStep(cpuScalingStepCount); mLayout.addDeviceSectionCpuTimeByCluster(mCpuScalingPolicies.getPolicies().length); - mLayout.addDeviceSectionUptime(); + mLayout.addDeviceSectionUsageDuration(); mLayout.addDeviceSectionEnergyConsumers(mCpuEnergyConsumerIds.length); mLayout.addDeviceSectionPowerEstimate(); mLayout.addUidSectionCpuTimeByPowerBracket(scalingStepToPowerBracketMap); @@ -451,6 +322,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { mTempUidStats = new long[mLayout.getCpuPowerBracketCount()]; mIsInitialized = true; + return true; } private void readCpuEnergyConsumerIds() { @@ -590,7 +462,9 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { * Prints the definitions of power brackets. */ public void dumpCpuPowerBracketsLocked(PrintWriter pw) { - ensureInitialized(); + if (!ensureInitialized()) { + return; + } if (mLayout == null) { return; @@ -610,7 +484,9 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { */ @VisibleForTesting public String getCpuPowerBracketDescription(int powerBracket) { - ensureInitialized(); + if (!ensureInitialized()) { + return ""; + } int[] stepToPowerBracketMap = mLayout.getScalingStepToPowerBracketMap(); StringBuilder sb = new StringBuilder(); @@ -647,14 +523,18 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { */ @VisibleForTesting public PowerStats.Descriptor getPowerStatsDescriptor() { - ensureInitialized(); + if (!ensureInitialized()) { + return null; + } return mPowerStatsDescriptor; } @Override protected PowerStats collectStats() { - ensureInitialized(); + if (!ensureInitialized()) { + return null; + } if (!mIsPerUidTimeInStateSupported) { return null; @@ -682,7 +562,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { if (uptimeDelta > mCpuPowerStats.durationMs) { uptimeDelta = mCpuPowerStats.durationMs; } - mLayout.setUptime(mCpuPowerStats.stats, uptimeDelta); + mLayout.setUsageDuration(mCpuPowerStats.stats, uptimeDelta); if (mCpuEnergyConsumerIds.length != 0) { collectEnergyConsumers(); @@ -761,7 +641,21 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { uidStats.timeByPowerBracket[bracket] = timeByPowerBracket[bracket]; } if (nonzero) { - mCpuPowerStats.uidStats.put(uid, uidStats.stats); + int ownerUid; + if (Process.isSdkSandboxUid(uid)) { + ownerUid = Process.getAppUidForSdkSandboxUid(uid); + } else { + ownerUid = mUidResolver.mapUid(uid); + } + + long[] ownerStats = mCpuPowerStats.uidStats.get(ownerUid); + if (ownerStats == null) { + mCpuPowerStats.uidStats.put(ownerUid, uidStats.stats); + } else { + for (int i = 0; i < ownerStats.length; i++) { + ownerStats[i] += uidStats.stats[i]; + } + } } } diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 2c7843e626c9..0facb9c01d74 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -44,6 +44,7 @@ class PowerComponentAggregatedPowerStats { private static final String XML_TAG_DEVICE_STATS = "device-stats"; private static final String XML_TAG_UID_STATS = "uid-stats"; private static final String XML_ATTR_UID = "uid"; + private static final long UNKNOWN = -1; public final int powerComponentId; private final MultiStateStats.States[] mDeviceStateConfig; @@ -51,17 +52,16 @@ class PowerComponentAggregatedPowerStats { @NonNull private final AggregatedPowerStatsConfig.PowerComponent mConfig; private final int[] mDeviceStates; - private final long[] mDeviceStateTimestamps; private MultiStateStats.Factory mStatsFactory; private MultiStateStats.Factory mUidStatsFactory; private PowerStats.Descriptor mPowerStatsDescriptor; + private long mPowerStatsTimestamp; private MultiStateStats mDeviceStats; private final SparseArray<UidStats> mUidStats = new SparseArray<>(); private static class UidStats { public int[] states; - public long[] stateTimestampMs; public MultiStateStats stats; } @@ -71,7 +71,7 @@ class PowerComponentAggregatedPowerStats { mDeviceStateConfig = config.getDeviceStateConfig(); mUidStateConfig = config.getUidStateConfig(); mDeviceStates = new int[mDeviceStateConfig.length]; - mDeviceStateTimestamps = new long[mDeviceStateConfig.length]; + mPowerStatsTimestamp = UNKNOWN; } @NonNull @@ -85,8 +85,11 @@ class PowerComponentAggregatedPowerStats { } void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) { + if (mDeviceStats == null) { + createDeviceStats(); + } + mDeviceStates[stateId] = state; - mDeviceStateTimestamps[stateId] = time; if (mDeviceStateConfig[stateId].isTracked()) { if (mDeviceStats != null) { @@ -97,6 +100,11 @@ class PowerComponentAggregatedPowerStats { if (mUidStateConfig[stateId].isTracked()) { for (int i = mUidStats.size() - 1; i >= 0; i--) { PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i); + if (uidStats.stats == null) { + createUidStats(uidStats); + } + + uidStats.states[stateId] = state; if (uidStats.stats != null) { uidStats.stats.setState(stateId, state, time); } @@ -111,8 +119,11 @@ class PowerComponentAggregatedPowerStats { } UidStats uidStats = getUidStats(uid); + if (uidStats.stats == null) { + createUidStats(uidStats); + } + uidStats.states[stateId] = state; - uidStats.stateTimestampMs[stateId] = time; if (uidStats.stats != null) { uidStats.stats.setState(stateId, state, time); @@ -150,10 +161,11 @@ class PowerComponentAggregatedPowerStats { } uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs); } + + mPowerStatsTimestamp = timestampMs; } void reset() { - mPowerStatsDescriptor = null; mStatsFactory = null; mUidStatsFactory = null; mDeviceStats = null; @@ -163,12 +175,10 @@ class PowerComponentAggregatedPowerStats { } private UidStats getUidStats(int uid) { - // TODO(b/292247660): map isolated and sandbox UIDs UidStats uidStats = mUidStats.get(uid); if (uidStats == null) { uidStats = new UidStats(); uidStats.states = new int[mUidStateConfig.length]; - uidStats.stateTimestampMs = new long[mUidStateConfig.length]; mUidStats.put(uid, uidStats); } return uidStats; @@ -209,42 +219,38 @@ class PowerComponentAggregatedPowerStats { return false; } - private boolean createDeviceStats() { + private void createDeviceStats() { if (mStatsFactory == null) { if (mPowerStatsDescriptor == null) { - return false; + return; } mStatsFactory = new MultiStateStats.Factory( mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig); } mDeviceStats = mStatsFactory.create(); - for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) { - mDeviceStats.setState(stateId, mDeviceStates[stateId], - mDeviceStateTimestamps[stateId]); + if (mPowerStatsTimestamp != UNKNOWN) { + for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) { + mDeviceStats.setState(stateId, mDeviceStates[stateId], mPowerStatsTimestamp); + } } - return true; } - private boolean createUidStats(UidStats uidStats) { + private void createUidStats(UidStats uidStats) { if (mUidStatsFactory == null) { if (mPowerStatsDescriptor == null) { - return false; + return; } mUidStatsFactory = new MultiStateStats.Factory( mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig); } uidStats.stats = mUidStatsFactory.create(); - for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) { - uidStats.stats.setState(stateId, mDeviceStates[stateId], - mDeviceStateTimestamps[stateId]); - } - for (int stateId = mDeviceStateConfig.length; stateId < mUidStateConfig.length; stateId++) { - uidStats.stats.setState(stateId, uidStats.states[stateId], - uidStats.stateTimestampMs[stateId]); + for (int stateId = 0; stateId < mUidStateConfig.length; stateId++) { + if (mPowerStatsTimestamp != UNKNOWN) { + uidStats.stats.setState(stateId, uidStats.states[stateId], mPowerStatsTimestamp); + } } - return true; } public void writeXml(TypedXmlSerializer serializer) throws IOException { diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java index 2f9d5674d78a..3f88a2d2dec0 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java @@ -29,13 +29,18 @@ import java.util.function.Consumer; * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history. */ public class PowerStatsAggregator { + private static final long UNINITIALIZED = -1; private final AggregatedPowerStats mStats; + private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; private final BatteryStatsHistory mHistory; private final SparseArray<AggregatedPowerStatsProcessor> mProcessors = new SparseArray<>(); + private int mCurrentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY; + private int mCurrentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig, BatteryStatsHistory history) { mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig); + mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig; mHistory = history; for (AggregatedPowerStatsConfig.PowerComponent powerComponentsConfig : aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs()) { @@ -44,6 +49,10 @@ public class PowerStatsAggregator { } } + AggregatedPowerStatsConfig getConfig() { + return mAggregatedPowerStatsConfig; + } + /** * Iterates of the battery history and aggregates power stats between the specified times. * The start and end are specified in the battery-stats monotonic time, which is the @@ -58,18 +67,20 @@ public class PowerStatsAggregator { */ public void aggregatePowerStats(long startTimeMs, long endTimeMs, Consumer<AggregatedPowerStats> consumer) { - int currentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY; - int currentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; - long baseTime = -1; + boolean clockUpdateAdded = false; + long baseTime = startTimeMs > 0 ? startTimeMs : UNINITIALIZED; long lastTime = 0; try (BatteryStatsHistoryIterator iterator = mHistory.copy().iterate(startTimeMs, endTimeMs)) { while (iterator.hasNext()) { BatteryStats.HistoryItem item = iterator.next(); - if (baseTime < 0) { + if (!clockUpdateAdded) { mStats.addClockUpdate(item.time, item.currentTime); - baseTime = item.time; + if (baseTime == UNINITIALIZED) { + baseTime = item.time; + } + clockUpdateAdded = true; } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME || item.cmd == BatteryStats.HistoryItem.CMD_RESET) { mStats.addClockUpdate(item.time, item.currentTime); @@ -81,20 +92,20 @@ public class PowerStatsAggregator { (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0 ? AggregatedPowerStatsConfig.POWER_STATE_OTHER : AggregatedPowerStatsConfig.POWER_STATE_BATTERY; - if (batteryState != currentBatteryState) { + if (batteryState != mCurrentBatteryState) { mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState, item.time); - currentBatteryState = batteryState; + mCurrentBatteryState = batteryState; } int screenState = (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0 ? AggregatedPowerStatsConfig.SCREEN_STATE_ON : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; - if (screenState != currentScreenState) { + if (screenState != mCurrentScreenState) { mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState, item.time); - currentScreenState = screenState; + mCurrentScreenState = screenState; } if (item.processStateChange != null) { diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java index 84cc21e81536..abfe9debc7de 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java @@ -16,10 +16,13 @@ package com.android.server.power.stats; +import android.annotation.Nullable; import android.os.ConditionVariable; import android.os.Handler; +import android.os.PersistableBundle; import android.util.FastImmutableArraySet; import android.util.IndentingPrintWriter; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.Clock; @@ -38,6 +41,7 @@ import java.util.stream.Stream; * except where noted. */ public abstract class PowerStatsCollector { + private static final String TAG = "PowerStatsCollector"; private static final int MILLIVOLTS_PER_VOLT = 1000; private final Handler mHandler; protected final Clock mClock; @@ -46,6 +50,200 @@ public abstract class PowerStatsCollector { private boolean mEnabled; private long mLastScheduledUpdateMs = -1; + /** + * Captures the positions and lengths of sections of the stats array, such as usage duration, + * power usage estimates etc. + */ + public static class StatsArrayLayout { + private static final String EXTRA_DEVICE_POWER_POSITION = "dp"; + private static final String EXTRA_DEVICE_DURATION_POSITION = "dd"; + private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de"; + private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec"; + private static final String EXTRA_UID_POWER_POSITION = "up"; + + protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; + + private int mDeviceStatsArrayLength; + private int mUidStatsArrayLength; + + protected int mDeviceDurationPosition; + private int mDeviceEnergyConsumerPosition; + private int mDeviceEnergyConsumerCount; + private int mDevicePowerEstimatePosition; + private int mUidPowerEstimatePosition; + + public int getDeviceStatsArrayLength() { + return mDeviceStatsArrayLength; + } + + public int getUidStatsArrayLength() { + return mUidStatsArrayLength; + } + + protected int addDeviceSection(int length) { + int position = mDeviceStatsArrayLength; + mDeviceStatsArrayLength += length; + return position; + } + + protected int addUidSection(int length) { + int position = mUidStatsArrayLength; + mUidStatsArrayLength += length; + return position; + } + + /** + * Declare that the stats array has a section capturing usage duration + */ + public void addDeviceSectionUsageDuration() { + mDeviceDurationPosition = addDeviceSection(1); + } + + /** + * Saves the usage duration in the corresponding <code>stats</code> element. + */ + public void setUsageDuration(long[] stats, long value) { + stats[mDeviceDurationPosition] = value; + } + + /** + * Extracts the usage duration from the corresponding <code>stats</code> element. + */ + public long getUsageDuration(long[] stats) { + return stats[mDeviceDurationPosition]; + } + + /** + * Declares that the stats array has a section capturing EnergyConsumer data from + * PowerStatsService. + */ + public void addDeviceSectionEnergyConsumers(int energyConsumerCount) { + mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount); + mDeviceEnergyConsumerCount = energyConsumerCount; + } + + public int getEnergyConsumerCount() { + return mDeviceEnergyConsumerCount; + } + + /** + * Saves the accumulated energy for the specified rail the corresponding + * <code>stats</code> element. + */ + public void setConsumedEnergy(long[] stats, int index, long energy) { + stats[mDeviceEnergyConsumerPosition + index] = energy; + } + + /** + * Extracts the EnergyConsumer data from a device stats array for the specified + * EnergyConsumer. + */ + public long getConsumedEnergy(long[] stats, int index) { + return stats[mDeviceEnergyConsumerPosition + index]; + } + + /** + * Declare that the stats array has a section capturing a power estimate + */ + public void addDeviceSectionPowerEstimate() { + mDevicePowerEstimatePosition = addDeviceSection(1); + } + + /** + * Converts the supplied mAh power estimate to a long and saves it in the corresponding + * element of <code>stats</code>. + */ + public void setDevicePowerEstimate(long[] stats, double power) { + stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); + } + + /** + * Extracts the power estimate from a device stats array and converts it to mAh. + */ + public double getDevicePowerEstimate(long[] stats) { + return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; + } + + /** + * Declare that the UID stats array has a section capturing a power estimate + */ + public void addUidSectionPowerEstimate() { + mUidPowerEstimatePosition = addUidSection(1); + } + + /** + * Converts the supplied mAh power estimate to a long and saves it in the corresponding + * element of <code>stats</code>. + */ + public void setUidPowerEstimate(long[] stats, double power) { + stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); + } + + /** + * Extracts the power estimate from a UID stats array and converts it to mAh. + */ + public double getUidPowerEstimate(long[] stats) { + return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; + } + + /** + * Copies the elements of the stats array layout into <code>extras</code> + */ + public void toExtras(PersistableBundle extras) { + extras.putInt(EXTRA_DEVICE_DURATION_POSITION, mDeviceDurationPosition); + extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION, + mDeviceEnergyConsumerPosition); + extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT, + mDeviceEnergyConsumerCount); + extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition); + extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition); + } + + /** + * Retrieves elements of the stats array layout from <code>extras</code> + */ + public void fromExtras(PersistableBundle extras) { + mDeviceDurationPosition = extras.getInt(EXTRA_DEVICE_DURATION_POSITION); + mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION); + mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT); + mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION); + mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION); + } + + protected void putIntArray(PersistableBundle extras, String key, int[] array) { + if (array == null) { + return; + } + + StringBuilder sb = new StringBuilder(); + for (int value : array) { + if (!sb.isEmpty()) { + sb.append(','); + } + sb.append(value); + } + extras.putString(key, sb.toString()); + } + + protected int[] getIntArray(PersistableBundle extras, String key) { + String string = extras.getString(key); + if (string == null) { + return null; + } + String[] values = string.trim().split(","); + int[] result = new int[values.length]; + for (int i = 0; i < values.length; i++) { + try { + result[i] = Integer.parseInt(values[i]); + } catch (NumberFormatException e) { + Slog.wtf(TAG, "Invalid CSV format: " + string); + return null; + } + } + return result; + } + } + @GuardedBy("this") @SuppressWarnings("unchecked") private volatile FastImmutableArraySet<Consumer<PowerStats>> mConsumerList = @@ -141,6 +339,7 @@ public abstract class PowerStatsCollector { return true; } + @Nullable protected abstract PowerStats collectStats(); /** diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java new file mode 100644 index 000000000000..70c24d58bb2a --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.power.stats; + +import android.os.AggregateBatteryConsumer; +import android.os.BatteryConsumer; +import android.os.BatteryUsageStats; +import android.os.UidBatteryConsumer; +import android.util.Slog; + +import com.android.internal.os.MultiStateStats; +import com.android.internal.os.PowerStats; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Given a time range, converts accumulated PowerStats to BatteryUsageStats. Combines + * stores spans of PowerStats with the yet-unprocessed tail of battery history. + */ +public class PowerStatsExporter { + private static final String TAG = "PowerStatsExporter"; + private final PowerStatsStore mPowerStatsStore; + private final PowerStatsAggregator mPowerStatsAggregator; + private final long mBatterySessionTimeSpanSlackMillis; + private static final long BATTERY_SESSION_TIME_SPAN_SLACK_MILLIS = TimeUnit.MINUTES.toMillis(2); + + public PowerStatsExporter(PowerStatsStore powerStatsStore, + PowerStatsAggregator powerStatsAggregator) { + this(powerStatsStore, powerStatsAggregator, BATTERY_SESSION_TIME_SPAN_SLACK_MILLIS); + } + + public PowerStatsExporter(PowerStatsStore powerStatsStore, + PowerStatsAggregator powerStatsAggregator, + long batterySessionTimeSpanSlackMillis) { + mPowerStatsStore = powerStatsStore; + mPowerStatsAggregator = powerStatsAggregator; + mBatterySessionTimeSpanSlackMillis = batterySessionTimeSpanSlackMillis; + } + + /** + * Populates the provided BatteryUsageStats.Builder with power estimates from the accumulated + * PowerStats, both stored in PowerStatsStore and not-yet processed. + */ + public void exportAggregatedPowerStats(BatteryUsageStats.Builder batteryUsageStatsBuilder, + long monotonicStartTime, long monotonicEndTime) { + long maxEndTime = monotonicStartTime; + List<PowerStatsSpan.Metadata> spans = mPowerStatsStore.getTableOfContents(); + for (int i = spans.size() - 1; i >= 0; i--) { + PowerStatsSpan.Metadata metadata = spans.get(i); + if (!metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) { + continue; + } + + List<PowerStatsSpan.TimeFrame> timeFrames = metadata.getTimeFrames(); + long spanMinTime = Long.MAX_VALUE; + long spanMaxTime = Long.MIN_VALUE; + for (int j = 0; j < timeFrames.size(); j++) { + PowerStatsSpan.TimeFrame timeFrame = timeFrames.get(j); + long startMonotonicTime = timeFrame.startMonotonicTime; + long endMonotonicTime = startMonotonicTime + timeFrame.duration; + if (startMonotonicTime < spanMinTime) { + spanMinTime = startMonotonicTime; + } + if (endMonotonicTime > spanMaxTime) { + spanMaxTime = endMonotonicTime; + } + } + + if (!(spanMinTime >= monotonicStartTime && spanMaxTime < monotonicEndTime)) { + continue; + } + + if (spanMaxTime > maxEndTime) { + maxEndTime = spanMaxTime; + } + + PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(), + AggregatedPowerStatsSection.TYPE); + if (span == null) { + Slog.e(TAG, "Could not read PowerStatsStore section " + metadata); + continue; + } + List<PowerStatsSpan.Section> sections = span.getSections(); + for (int k = 0; k < sections.size(); k++) { + PowerStatsSpan.Section section = sections.get(k); + populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, + ((AggregatedPowerStatsSection) section).getAggregatedPowerStats()); + } + } + + if (maxEndTime < monotonicEndTime - mBatterySessionTimeSpanSlackMillis) { + mPowerStatsAggregator.aggregatePowerStats(maxEndTime, monotonicEndTime, + stats -> populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats)); + } + } + + private void populateBatteryUsageStatsBuilder( + BatteryUsageStats.Builder batteryUsageStatsBuilder, AggregatedPowerStats stats) { + AggregatedPowerStatsConfig config = mPowerStatsAggregator.getConfig(); + List<AggregatedPowerStatsConfig.PowerComponent> powerComponents = + config.getPowerComponentsAggregatedStatsConfigs(); + for (int i = powerComponents.size() - 1; i >= 0; i--) { + populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats, + powerComponents.get(i)); + } + } + + private void populateBatteryUsageStatsBuilder( + BatteryUsageStats.Builder batteryUsageStatsBuilder, AggregatedPowerStats stats, + AggregatedPowerStatsConfig.PowerComponent powerComponent) { + int powerComponentId = powerComponent.getPowerComponentId(); + PowerComponentAggregatedPowerStats powerComponentStats = stats.getPowerComponentStats( + powerComponentId); + if (powerComponentStats == null) { + return; + } + + PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor(); + if (descriptor == null) { + return; + } + + PowerStatsCollector.StatsArrayLayout layout = new PowerStatsCollector.StatsArrayLayout(); + layout.fromExtras(descriptor.extras); + + long[] deviceStats = new long[descriptor.statsArrayLength]; + double[] totalPower = new double[1]; + MultiStateStats.States.forEachTrackedStateCombination(powerComponent.getDeviceStateConfig(), + states -> { + if (states[AggregatedPowerStatsConfig.STATE_POWER] + != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) { + return; + } + + if (!powerComponentStats.getDeviceStats(deviceStats, states)) { + return; + } + + totalPower[0] += layout.getDevicePowerEstimate(deviceStats); + }); + + AggregateBatteryConsumer.Builder deviceScope = + batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); + deviceScope.addConsumedPower(powerComponentId, + totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED); + + long[] uidStats = new long[descriptor.uidStatsArrayLength]; + ArrayList<Integer> uids = new ArrayList<>(); + powerComponentStats.collectUids(uids); + + boolean breakDownByProcState = + batteryUsageStatsBuilder.isProcessStateDataNeeded() + && powerComponent + .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE] + .isTracked(); + + double[] powerByProcState = + new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1]; + double powerAllApps = 0; + for (int uid : uids) { + UidBatteryConsumer.Builder builder = + batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid); + + Arrays.fill(powerByProcState, 0); + + MultiStateStats.States.forEachTrackedStateCombination( + powerComponent.getUidStateConfig(), + states -> { + if (states[AggregatedPowerStatsConfig.STATE_POWER] + != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) { + return; + } + + if (!powerComponentStats.getUidStats(uidStats, uid, states)) { + return; + } + + double power = layout.getUidPowerEstimate(uidStats); + int procState = breakDownByProcState + ? states[AggregatedPowerStatsConfig.STATE_PROCESS_STATE] + : BatteryConsumer.PROCESS_STATE_UNSPECIFIED; + powerByProcState[procState] += power; + }); + + double powerAllProcStates = 0; + for (int procState = 0; procState < powerByProcState.length; procState++) { + double power = powerByProcState[procState]; + if (power == 0) { + continue; + } + powerAllProcStates += power; + if (breakDownByProcState + && procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + builder.addConsumedPower(builder.getKey(powerComponentId, procState), + power, BatteryConsumer.POWER_MODEL_UNDEFINED); + } + } + builder.addConsumedPower(powerComponentId, powerAllProcStates, + BatteryConsumer.POWER_MODEL_UNDEFINED); + powerAllApps += powerAllProcStates; + } + + AggregateBatteryConsumer.Builder allAppsScope = + batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); + allAppsScope.addConsumedPower(powerComponentId, powerAllApps, + BatteryConsumer.POWER_MODEL_UNDEFINED); + } +} diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java index 551302ee8f14..97d872a1a539 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java @@ -19,8 +19,6 @@ package com.android.server.power.stats; import android.annotation.DurationMillisLong; import android.app.AlarmManager; import android.content.Context; -import android.os.BatteryUsageStats; -import android.os.BatteryUsageStatsQuery; import android.os.ConditionVariable; import android.os.Handler; import android.util.IndentingPrintWriter; @@ -52,7 +50,6 @@ public class PowerStatsScheduler { private final MonotonicClock mMonotonicClock; private final Handler mHandler; private final BatteryStatsImpl mBatteryStats; - private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; private final PowerStatsAggregator mPowerStatsAggregator; private long mLastSavedSpanEndMonotonicTime; @@ -60,7 +57,7 @@ public class PowerStatsScheduler { @DurationMillisLong long aggregatedPowerStatsSpanDuration, @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore, Clock clock, MonotonicClock monotonicClock, Handler handler, - BatteryStatsImpl batteryStats, BatteryUsageStatsProvider batteryUsageStatsProvider) { + BatteryStatsImpl batteryStats) { mContext = context; mPowerStatsAggregator = powerStatsAggregator; mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration; @@ -70,16 +67,15 @@ public class PowerStatsScheduler { mMonotonicClock = monotonicClock; mHandler = handler; mBatteryStats = batteryStats; - mBatteryUsageStatsProvider = batteryUsageStatsProvider; } /** * Kicks off the scheduling of power stats aggregation spans. */ public void start(boolean enablePeriodicPowerStatsCollection) { - mBatteryStats.setBatteryResetListener(this::storeBatteryUsageStatsOnReset); mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection; if (mEnablePeriodicPowerStatsCollection) { + schedulePowerStatsAggregation(); scheduleNextPowerStatsAggregation(); } } @@ -235,28 +231,6 @@ public class PowerStatsScheduler { mPowerStatsStore.storeAggregatedPowerStats(stats); } - private void storeBatteryUsageStatsOnReset(int resetReason) { - if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) { - return; - } - - final BatteryUsageStats batteryUsageStats = - mBatteryUsageStatsProvider.getBatteryUsageStats( - new BatteryUsageStatsQuery.Builder() - .setMaxStatsAgeMs(0) - .includePowerModels() - .includeProcessStateData() - .build()); - - // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end - // Once that change is made, we will be able to use the BatteryUsageStats' monotonic - // start time - long monotonicStartTime = - mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration(); - mHandler.post(() -> - mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats)); - } - private void awaitCompletion() { ConditionVariable done = new ConditionVariable(); mHandler.post(done::open); diff --git a/services/core/java/com/android/server/power/stats/PowerStatsUidResolver.java b/services/core/java/com/android/server/power/stats/PowerStatsUidResolver.java new file mode 100644 index 000000000000..8dc360983239 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsUidResolver.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.power.stats; + +import android.util.IntArray; +import android.util.Slog; +import android.util.SparseIntArray; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Maintains a map of isolated UIDs to their respective owner UIDs, to support combining + * power stats for isolated UIDs, which are typically short-lived, into the corresponding app UID. + */ +public class PowerStatsUidResolver { + private static final String TAG = "PowerStatsUidResolver"; + + /** + * Listener notified when isolated UIDs are created and removed. + */ + public interface Listener { + + /** + * Callback invoked when a new isolated UID is registered. + */ + void onIsolatedUidAdded(int isolatedUid, int parentUid); + + /** + * Callback invoked before an isolated UID is evicted from the resolver. + * If the listener calls {@link PowerStatsUidResolver#retainIsolatedUid}, the mapping + * will be retained until {@link PowerStatsUidResolver#releaseIsolatedUid} is called. + */ + void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid); + + /** + * Callback invoked when an isolated UID to owner UID mapping is removed. + */ + void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid); + } + + /** + * Mapping isolated uids to the actual owning app uid. + */ + private final SparseIntArray mIsolatedUids = new SparseIntArray(); + + /** + * Internal reference count of isolated uids. + */ + private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray(); + + // Keep the list read-only in order to avoid locking during the delivery of listener calls. + private volatile List<Listener> mListeners = Collections.emptyList(); + + /** + * Adds a listener. + */ + public void addListener(Listener listener) { + synchronized (this) { + List<Listener> newList = new ArrayList<>(mListeners); + newList.add(listener); + mListeners = Collections.unmodifiableList(newList); + } + } + + /** + * Removes a listener. + */ + public void removeListener(Listener listener) { + synchronized (this) { + List<Listener> newList = new ArrayList<>(mListeners); + newList.remove(listener); + mListeners = Collections.unmodifiableList(newList); + } + } + + /** + * Remembers the connection between a newly created isolated UID and its owner app UID. + * Calls {@link Listener#onIsolatedUidAdded} on each registered listener. + */ + public void noteIsolatedUidAdded(int isolatedUid, int parentUid) { + synchronized (this) { + mIsolatedUids.put(isolatedUid, parentUid); + mIsolatedUidRefCounts.put(isolatedUid, 1); + } + + List<Listener> listeners = mListeners; + for (int i = listeners.size() - 1; i >= 0; i--) { + listeners.get(i).onIsolatedUidAdded(isolatedUid, parentUid); + } + } + + /** + * Handles the removal of an isolated UID by invoking + * {@link Listener#onBeforeIsolatedUidRemoved} on each registered listener and the releases + * the UID, see {@link #releaseIsolatedUid}. + */ + public void noteIsolatedUidRemoved(int isolatedUid, int parentUid) { + synchronized (this) { + int curUid = mIsolatedUids.get(isolatedUid, -1); + if (curUid != parentUid) { + Slog.wtf(TAG, "Attempt to remove an isolated UID " + isolatedUid + + " with the parent UID " + parentUid + + ". The registered parent UID is " + curUid); + return; + } + } + + List<Listener> listeners = mListeners; + for (int i = listeners.size() - 1; i >= 0; i--) { + listeners.get(i).onBeforeIsolatedUidRemoved(isolatedUid, parentUid); + } + + releaseIsolatedUid(isolatedUid); + } + + /** + * Increments the ref count for an isolated uid. + * Call #releaseIsolatedUid to decrement. + */ + public void retainIsolatedUid(int uid) { + synchronized (this) { + final int refCount = mIsolatedUidRefCounts.get(uid, 0); + if (refCount <= 0) { + // Uid is not mapped or referenced + Slog.w(TAG, + "Attempted to increment ref counted of untracked isolated uid (" + uid + + ")"); + return; + } + mIsolatedUidRefCounts.put(uid, refCount + 1); + } + } + + /** + * Decrements the ref count for the given isolated UID. If the ref count drops to zero, + * removes the mapping and calls {@link Listener#onAfterIsolatedUidRemoved} on each registered + * listener. + */ + public void releaseIsolatedUid(int isolatedUid) { + int parentUid; + synchronized (this) { + final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1; + if (refCount > 0) { + // Isolated uid is still being tracked + mIsolatedUidRefCounts.put(isolatedUid, refCount); + return; + } + + final int idx = mIsolatedUids.indexOfKey(isolatedUid); + if (idx >= 0) { + parentUid = mIsolatedUids.valueAt(idx); + mIsolatedUids.removeAt(idx); + mIsolatedUidRefCounts.delete(isolatedUid); + } else { + Slog.w(TAG, "Attempted to remove untracked child uid (" + isolatedUid + ")"); + return; + } + } + + List<Listener> listeners = mListeners; + for (int i = listeners.size() - 1; i >= 0; i--) { + listeners.get(i).onAfterIsolatedUidRemoved(isolatedUid, parentUid); + } + } + + /** + * Releases all isolated UIDs in the specified range, both ends inclusive. + */ + public void releaseUidsInRange(int startUid, int endUid) { + IntArray toRelease; + synchronized (this) { + int startIndex = mIsolatedUids.indexOfKey(startUid); + int endIndex = mIsolatedUids.indexOfKey(endUid); + + if (startIndex < 0) { + startIndex = ~startIndex; + } + + if (endIndex < 0) { + // In this ~endIndex is pointing just past where endUid would be, so we must -1. + endIndex = ~endIndex - 1; + } + + if (startIndex > endIndex) { + return; + } + + toRelease = new IntArray(endIndex - startIndex); + for (int i = startIndex; i <= endIndex; i++) { + toRelease.add(mIsolatedUids.keyAt(i)); + } + } + + for (int i = toRelease.size() - 1; i >= 0; i--) { + releaseIsolatedUid(toRelease.get(i)); + } + } + + /** + * Given an isolated UID, returns the corresponding owner UID. For a non-isolated + * UID, returns the UID itself. + */ + public int mapUid(int uid) { + synchronized (this) { + return mIsolatedUids.get(/*key=*/uid, /*valueIfKeyNotFound=*/uid); + } + } + + /** + * Dumps the current contents of the resolver for the sake of dumpsys. + */ + public void dump(PrintWriter pw) { + pw.println("Currently mapped isolated uids:"); + synchronized (this) { + final int numIsolatedUids = mIsolatedUids.size(); + for (int i = 0; i < numIsolatedUids; i++) { + final int isolatedUid = mIsolatedUids.keyAt(i); + final int ownerUid = mIsolatedUids.valueAt(i); + final int refs = mIsolatedUidRefCounts.get(isolatedUid); + pw.println(" " + isolatedUid + "->" + ownerUid + " (ref count = " + refs + ")"); + } + } + } +} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index f8078d2e9f27..e088d9afd67d 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -284,7 +284,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub + " needsUpdate=" + needsUpdate); } - int notifyColorsWhich = 0; synchronized (mLock) { notifyCallbacksLocked(wallpaper); @@ -338,7 +337,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // If this was the system wallpaper, rebind... bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper, callback); - notifyColorsWhich |= wallpaper.mWhich; } if (lockWallpaperChanged) { @@ -358,7 +356,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub bindWallpaperComponentLocked(mImageWallpaper, true /* force */, false /* fromUser */, wallpaper, callback); - notifyColorsWhich |= FLAG_LOCK; } else if (isAppliedToLock) { // This is system-plus-lock: we need to wipe the lock bookkeeping since // we're falling back to displaying the system wallpaper there. @@ -372,7 +369,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } clearWallpaperBitmaps(mWallpaper.userId, FLAG_LOCK); mLockWallpaperMap.remove(wallpaper.userId); - notifyColorsWhich |= FLAG_LOCK; } saveSettingsLocked(wallpaper.userId); @@ -382,9 +378,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // Outside of the lock since it will synchronize itself - if (notifyColorsWhich != 0) { - notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich); - } + notifyWallpaperColorsChanged(wallpaper); } @Override @@ -406,16 +400,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) { + void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper) { if (DEBUG) { Slog.i(TAG, "Notifying wallpaper colors changed"); } if (wallpaper.connection != null) { - wallpaper.connection.forEachDisplayConnector(connector -> { - notifyWallpaperColorsChangedOnDisplay(wallpaper, which, connector.mDisplayId); - }); - } else { // Lock wallpaper does not have WallpaperConnection. - notifyWallpaperColorsChangedOnDisplay(wallpaper, which, DEFAULT_DISPLAY); + wallpaper.connection.forEachDisplayConnector(connector -> + notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId)); } } @@ -430,7 +421,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return listeners; } - private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper, int which, + private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper, int displayId) { boolean needsExtraction; synchronized (mLock) { @@ -445,17 +436,20 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } if (DEBUG) { - Slog.v(TAG, "notifyWallpaperColorsChangedOnDisplay " + which); + Slog.v(TAG, "notifyWallpaperColorsChangedOnDisplay " + wallpaper.mWhich); } needsExtraction = wallpaper.primaryColors == null || wallpaper.mIsColorExtractedFromDim; } + boolean notify = true; if (needsExtraction) { - extractColors(wallpaper); + notify = extractColors(wallpaper); + } + if (notify) { + notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), + wallpaper.mWhich, wallpaper.userId, displayId); } - notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), which, - wallpaper.userId, displayId); } private static <T extends IInterface> boolean emptyCallbackList(RemoteCallbackList<T> list) { @@ -505,8 +499,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * In this case, using the crop is more than enough. Live wallpapers are just ignored. * * @param wallpaper a wallpaper representation + * @return true unless the wallpaper changed during the color computation */ - private void extractColors(WallpaperData wallpaper) { + private boolean extractColors(WallpaperData wallpaper) { String cropFile = null; boolean defaultImageWallpaper = false; int wallpaperId; @@ -518,13 +513,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (wallpaper.equals(mFallbackWallpaper)) { synchronized (mLock) { - if (mFallbackWallpaper.primaryColors != null) return; + if (mFallbackWallpaper.primaryColors != null) return true; } final WallpaperColors colors = extractDefaultImageWallpaperColors(wallpaper); synchronized (mLock) { mFallbackWallpaper.primaryColors = colors; } - return; + return true; } synchronized (mLock) { @@ -554,7 +549,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (colors == null) { Slog.w(TAG, "Cannot extract colors because wallpaper could not be read."); - return; + return true; } synchronized (mLock) { @@ -563,8 +558,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // Now that we have the colors, let's save them into the xml // to avoid having to run this again. saveSettingsLocked(wallpaper.userId); + return true; } else { Slog.w(TAG, "Not setting primary colors since wallpaper changed"); + return false; } } } @@ -1138,19 +1135,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub */ @Override public void onWallpaperColorsChanged(WallpaperColors primaryColors, int displayId) { - int which; synchronized (mLock) { // Do not broadcast changes on ImageWallpaper since it's handled // internally by this class. if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) { return; } - which = mWallpaper.mWhich; mWallpaper.primaryColors = primaryColors; } - if (which != 0) { - notifyWallpaperColorsChangedOnDisplay(mWallpaper, which, displayId); - } + notifyWallpaperColorsChangedOnDisplay(mWallpaper, displayId); } @Override @@ -1794,9 +1787,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // Offload color extraction to another thread since switchUser will be called // from the main thread. FgThread.getHandler().post(() -> { - notifyWallpaperColorsChanged(systemWallpaper, FLAG_SYSTEM); - notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK); - notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM); + notifyWallpaperColorsChanged(systemWallpaper); + if (lockWallpaper != systemWallpaper) notifyWallpaperColorsChanged(lockWallpaper); + notifyWallpaperColorsChanged(mFallbackWallpaper); }); } finally { t.traceEnd(); @@ -1873,12 +1866,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub data = mWallpaperMap.get(userId); } } - - // When clearing a wallpaper, broadcast new valid colors - if (data != null) { - notifyWallpaperColorsChanged(data, which); - notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM); - } } private void clearWallpaperLocked(int which, int userId, boolean fromForeground, @@ -2650,7 +2637,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } for (WallpaperData wp: pendingColorExtraction) { - notifyWallpaperColorsChanged(wp, wp.mWhich); + notifyWallpaperColorsChanged(wp); } } finally { Binder.restoreCallingIdentity(ident); @@ -3030,8 +3017,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } if (shouldNotifyColors) { - notifyWallpaperColorsChanged(newWallpaper, which); - notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM); + notifyWallpaperColorsChanged(newWallpaper); } return bindSuccess; } diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java index 68f554cb2758..ea8a801ff697 100644 --- a/services/core/java/com/android/server/webkit/SystemImpl.java +++ b/services/core/java/com/android/server/webkit/SystemImpl.java @@ -16,6 +16,8 @@ package com.android.server.webkit; +import static android.webkit.Flags.updateServiceV2; + import android.app.ActivityManager; import android.app.AppGlobals; import android.content.Context; @@ -237,18 +239,30 @@ public class SystemImpl implements SystemInterface { @Override public int getMultiProcessSetting(Context context) { - return Settings.Global.getInt(context.getContentResolver(), - Settings.Global.WEBVIEW_MULTIPROCESS, 0); + if (updateServiceV2()) { + throw new IllegalStateException( + "getMultiProcessSetting shouldn't be called if update_service_v2 flag is set."); + } + return Settings.Global.getInt( + context.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, 0); } @Override public void setMultiProcessSetting(Context context, int value) { - Settings.Global.putInt(context.getContentResolver(), - Settings.Global.WEBVIEW_MULTIPROCESS, value); + if (updateServiceV2()) { + throw new IllegalStateException( + "setMultiProcessSetting shouldn't be called if update_service_v2 flag is set."); + } + Settings.Global.putInt( + context.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, value); } @Override public void notifyZygote(boolean enableMultiProcess) { + if (updateServiceV2()) { + throw new IllegalStateException( + "notifyZygote shouldn't be called if update_service_v2 flag is set."); + } WebViewZygote.setMultiprocessEnabled(enableMultiProcess); } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index b3672ecb194c..b12da61d0b3f 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -157,8 +157,13 @@ public class WebViewUpdateService extends SystemService { public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { - (new WebViewUpdateServiceShellCommand(this)).exec( - this, in, out, err, args, callback, resultReceiver); + if (updateServiceV2()) { + (new WebViewUpdateServiceShellCommand2(this)) + .exec(this, in, out, err, args, callback, resultReceiver); + } else { + (new WebViewUpdateServiceShellCommand(this)) + .exec(this, in, out, err, args, callback, resultReceiver); + } } @@ -275,18 +280,31 @@ public class WebViewUpdateService extends SystemService { @Override // Binder call public boolean isMultiProcessEnabled() { + if (updateServiceV2()) { + throw new IllegalStateException( + "isMultiProcessEnabled shouldn't be called if update_service_v2 flag is" + + " set."); + } return WebViewUpdateService.this.mImpl.isMultiProcessEnabled(); } @Override // Binder call public void enableMultiProcess(boolean enable) { - if (getContext().checkCallingPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS) + if (updateServiceV2()) { + throw new IllegalStateException( + "enableMultiProcess shouldn't be called if update_service_v2 flag is set."); + } + if (getContext() + .checkCallingPermission( + android.Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { - String msg = "Permission Denial: enableMultiProcess() from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() - + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS; + String msg = + "Permission Denial: enableMultiProcess() from pid=" + + Binder.getCallingPid() + + ", uid=" + + Binder.getCallingUid() + + " requires " + + android.Manifest.permission.WRITE_SECURE_SETTINGS; Slog.w(TAG, msg); throw new SecurityException(msg); } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index cfdef1471f83..26c3fba23bbc 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -17,6 +17,7 @@ package com.android.server.webkit; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; @@ -29,8 +30,12 @@ import android.webkit.WebViewFactory; import android.webkit.WebViewProviderInfo; import android.webkit.WebViewProviderResponse; +import com.android.server.LocalServices; +import com.android.server.PinnerService; + import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -88,6 +93,8 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE; private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE; + private static final String PIN_GROUP = "webview"; + private final SystemInterface mSystemInterface; private final Context mContext; @@ -339,6 +346,34 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { return newPackage; } + private void pinWebviewIfRequired(ApplicationInfo appInfo) { + PinnerService pinnerService = LocalServices.getService(PinnerService.class); + int webviewPinQuota = pinnerService.getWebviewPinQuota(); + if (webviewPinQuota <= 0) { + return; + } + + pinnerService.unpinGroup(PIN_GROUP); + + ArrayList<String> apksToPin = new ArrayList<>(); + boolean pinSharedFirst = appInfo.metaData.getBoolean("PIN_SHARED_LIBS_FIRST", true); + for (String sharedLib : appInfo.sharedLibraryFiles) { + apksToPin.add(sharedLib); + } + apksToPin.add(appInfo.sourceDir); + if (!pinSharedFirst) { + // We want to prioritize pinning of the native library that is most likely used by apps + // which in some build flavors live in the main apk and as a shared library for others. + Collections.reverse(apksToPin); + } + for (String apk : apksToPin) { + if (webviewPinQuota <= 0) { + break; + } + int bytesPinned = pinnerService.pinFile(apk, webviewPinQuota, appInfo, PIN_GROUP); + webviewPinQuota -= bytesPinned; + } + } /** * This is called when we change WebView provider, either when the current provider is * updated or a new provider is chosen / takes precedence. @@ -347,6 +382,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { synchronized (mLock) { mAnyWebViewInstalled = true; if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) { + pinWebviewIfRequired(newPackage.applicationInfo); mCurrentWebViewPackage = newPackage; // The relro creations might 'finish' (not start at all) before diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java index e618c7e2a80c..89cb4c802410 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java @@ -23,6 +23,7 @@ import android.content.pm.Signature; import android.os.AsyncTask; import android.os.Trace; import android.os.UserHandle; +import android.text.TextUtils; import android.util.Slog; import android.webkit.UserPackage; import android.webkit.WebViewFactory; @@ -70,10 +71,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { WebViewPackageMissingException(String message) { super(message); } - - WebViewPackageMissingException(Exception e) { - super(e); - } } private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000. @@ -85,9 +82,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { 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; @@ -166,7 +160,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { @Override public void prepareWebViewInSystemServer() { - mSystemInterface.notifyZygote(isMultiProcessEnabled()); try { synchronized (mLock) { mCurrentWebViewPackage = findPreferredWebViewPackage(); @@ -366,14 +359,10 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { // 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); - } + AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady); } - /** - * Fetch only the currently valid WebView packages. - **/ + /** Fetch only the currently valid WebView packages. */ @Override public WebViewProviderInfo[] getValidWebViewPackages() { ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos(); @@ -632,62 +621,56 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { @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; - } + throw new IllegalStateException( + "isMultiProcessEnabled shouldn't be called if update_service_v2 flag is set."); } @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); - } + throw new IllegalStateException( + "enableMultiProcess shouldn't be called if update_service_v2 flag is set."); } - /** - * Dump the state of this Service. - */ + /** 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( + TextUtils.formatSimple( + " 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)); + pw.println( + TextUtils.formatSimple( + " Minimum targetSdkVersion: %d", UserPackage.MINIMUM_SUPPORTED_SDK)); + pw.println( + TextUtils.formatSimple( + " Minimum WebView version code: %d", mMinimumVersionCode)); + pw.println( + TextUtils.formatSimple( + " Number of relros started: %d", mNumRelroCreationsStarted)); + pw.println( + TextUtils.formatSimple( + " Number of relros finished: %d", mNumRelroCreationsFinished)); + pw.println(TextUtils.formatSimple(" WebView package dirty: %b", mWebViewPackageDirty)); + pw.println( + TextUtils.formatSimple( + " 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)); + pw.println( + TextUtils.formatSimple( + " Preferred WebView package (name, version): (%s, %s)", + preferredWebViewPackage.packageName, + preferredWebViewPackage.versionName)); } catch (WebViewPackageMissingException e) { - pw.println(String.format(" Preferred WebView package: none")); + pw.println(" Preferred WebView package: none"); } dumpAllPackageInformationLocked(pw); @@ -703,29 +686,36 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { PackageInfo systemUserPackageInfo = userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo(); if (systemUserPackageInfo == null) { - pw.println(String.format(" %s is NOT installed.", provider.packageName)); + pw.println( + TextUtils.formatSimple(" %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); + String packageDetails = + TextUtils.formatSimple( + "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")); + boolean installedForAllUsers = + isInstalledAndEnabledForAllUsers( + mSystemInterface.getPackageInfoForProviderAllUsers( + mContext, provider)); + pw.println( + TextUtils.formatSimple( + " 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))); + pw.println( + TextUtils.formatSimple( + " Invalid package %s (%s), reason: %s", + systemUserPackageInfo.packageName, + packageDetails, + getInvalidityReason(validity))); } } } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand2.java new file mode 100644 index 000000000000..ce95b1857b27 --- /dev/null +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand2.java @@ -0,0 +1,91 @@ +/* + * 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.os.RemoteException; +import android.os.ShellCommand; +import android.text.TextUtils; +import android.webkit.IWebViewUpdateService; + +import java.io.PrintWriter; + +class WebViewUpdateServiceShellCommand2 extends ShellCommand { + final IWebViewUpdateService mInterface; + + WebViewUpdateServiceShellCommand2(IWebViewUpdateService service) { + mInterface = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + + final PrintWriter pw = getOutPrintWriter(); + try { + switch (cmd) { + case "set-webview-implementation": + return setWebViewImplementation(); + default: + return handleDefaultCommands(cmd); + } + } catch (RemoteException e) { + pw.println("Remote exception: " + e); + } + return -1; + } + + private int setWebViewImplementation() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + String shellChosenPackage = getNextArg(); + if (shellChosenPackage == null) { + pw.println("Failed to switch, no PACKAGE provided."); + pw.println(""); + helpSetWebViewImplementation(); + return 1; + } + String newPackage = mInterface.changeProviderAndSetting(shellChosenPackage); + if (shellChosenPackage.equals(newPackage)) { + pw.println("Success"); + return 0; + } else { + pw.println( + TextUtils.formatSimple( + "Failed to switch to %s, the WebView implementation is now provided by" + + " %s.", + shellChosenPackage, newPackage)); + return 1; + } + } + + public void helpSetWebViewImplementation() { + PrintWriter pw = getOutPrintWriter(); + pw.println(" set-webview-implementation PACKAGE"); + pw.println(" Set the WebView implementation to the specified package."); + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("WebView updater commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(""); + helpSetWebViewImplementation(); + pw.println(); + } +} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d90d4ff6bbd6..75e6faf97294 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -694,7 +694,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private boolean mCurrentLaunchCanTurnScreenOn = true; /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */ - private boolean mLastSurfaceShowing; + boolean mLastSurfaceShowing; /** * The activity is opaque and fills the entire space of this task. @@ -2565,7 +2565,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } if (abort) { - surface.remove(false /* prepareAnimation */); + surface.remove(false /* prepareAnimation */, false /* hasImeSurface */); } } else { ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Surface returned was null: %s", @@ -2731,7 +2731,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Receive the splash screen data from shell, sending to client. * @param parcelable The data to reconstruct the splash screen view, null mean unable to copy. */ - void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) { + void onCopySplashScreenFinish(@Nullable SplashScreenViewParcelable parcelable) { removeTransferSplashScreenTimeout(); final SurfaceControl windowAnimationLeash = (parcelable == null || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING @@ -2898,6 +2898,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final StartingSurfaceController.StartingSurface surface; final boolean animate; + final boolean hasImeSurface; if (mStartingData != null) { if (mStartingData.mWaitForSyncTransactionCommit || mTransitionController.isCollecting(this)) { @@ -2907,6 +2908,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } animate = prepareAnimation && mStartingData.needRevealAnimation() && mStartingWindow.isVisibleByPolicy(); + hasImeSurface = mStartingData.hasImeSurface(); ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Schedule remove starting %s startingWindow=%s" + " animate=%b Callers=%s", this, mStartingWindow, animate, Debug.getCallers(5)); @@ -2926,7 +2928,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A this); return; } - surface.remove(animate); + surface.remove(animate, hasImeSurface); } /** @@ -5380,11 +5382,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Finish should only ever commit visibility=false, so we can check full containment // rather than just direct membership. inFinishingTransition = mTransitionController.inFinishingTransition(this); - if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) { + if (!inFinishingTransition) { if (visible) { - mTransitionController.onVisibleWithoutCollectingTransition(this, - Debug.getCallers(1, 1)); - } else { + if (!mDisplayContent.isSleeping() || canShowWhenLocked()) { + mTransitionController.onVisibleWithoutCollectingTransition(this, + Debug.getCallers(1, 1)); + } + } else if (!mDisplayContent.isSleeping()) { Slog.w(TAG, "Set invisible without transition " + this); } } @@ -6434,20 +6438,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void stopIfPossible() { if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + this); - final Task rootTask = getRootTask(); + if (finishing) { + Slog.e(TAG, "Request to stop a finishing activity: " + this); + destroyIfPossible("stopIfPossible-finishing"); + return; + } if (isNoHistory()) { - if (!finishing) { - if (!rootTask.shouldSleepActivities()) { - ProtoLog.d(WM_DEBUG_STATES, "no-history finish of %s", this); - if (finishIfPossible("stop-no-history", false /* oomAdj */) - != FINISH_RESULT_CANCELLED) { - resumeKeyDispatchingLocked(); - return; - } - } else { - ProtoLog.d(WM_DEBUG_STATES, "Not finishing noHistory %s on stop " - + "because we're just sleeping", this); + if (!task.shouldSleepActivities()) { + ProtoLog.d(WM_DEBUG_STATES, "no-history finish of %s", this); + if (finishIfPossible("stop-no-history", false /* oomAdj */) + != FINISH_RESULT_CANCELLED) { + resumeKeyDispatchingLocked(); + return; } + } else { + ProtoLog.d(WM_DEBUG_STATES, "Not finishing noHistory %s on stop " + + "because we're just sleeping", this); } } diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index c61d86355c8b..1a197875ba31 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -88,9 +88,11 @@ class ActivityRecordInputSink { || activityBelowInTask.isUid(mActivityRecord.getUid())); if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition() || !mActivityRecord.mActivityRecordInputSinkEnabled) { + // Set to non-touchable, so the touch events can pass through. mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE, InputConfig.NOT_TOUCHABLE); } else { + // Set to touchable, so it can block by intercepting the touch events. mInputWindowHandleWrapper.setInputConfigMasked(0, InputConfig.NOT_TOUCHABLE); } mInputWindowHandleWrapper.setDisplayId(mActivityRecord.getDisplayId()); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 5e0a449ddd63..d6302e08fedb 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -103,6 +103,7 @@ import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.AuxiliaryResolveInfo; +import android.content.pm.Flags; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; @@ -128,6 +129,7 @@ import com.android.internal.app.IVoiceInteractor; import com.android.internal.protolog.common.ProtoLog; import com.android.server.am.PendingIntentRecord; import com.android.server.pm.InstantAppResolver; +import com.android.server.pm.PackageArchiver; import com.android.server.power.ShutdownCheckPoints; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; @@ -958,6 +960,17 @@ class ActivityStarter { } } + if (Flags.archiving()) { + PackageArchiver packageArchiver = mService + .getPackageManagerInternalLocked() + .getPackageArchiver(); + if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) { + return packageArchiver + .requestUnarchiveOnActivityStart( + intent, callingPackage, mRequest.userId, realCallingUid); + } + } + final int launchFlags = intent.getFlags(); if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { // Transfer the result target from the source activity to the new one being started, diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 34c7eee45f34..6f5c676187e1 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3618,8 +3618,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { * @hide */ @Override - public void onSplashScreenViewCopyFinished(int taskId, SplashScreenViewParcelable parcelable) - throws RemoteException { + public void onSplashScreenViewCopyFinished(int taskId, + @Nullable SplashScreenViewParcelable parcelable) + throws RemoteException { mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS, "copySplashScreenViewFinish()"); synchronized (mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 90eeed288240..a21b9b488004 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -55,6 +55,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; import static com.android.server.wm.ActivityRecord.State.PAUSED; import static com.android.server.wm.ActivityRecord.State.PAUSING; import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS; +import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IDLE; @@ -1681,7 +1682,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { ArrayList<ActivityRecord> activities = null; for (int i = mStoppingActivities.size() - 1; i >= 0; i--) { final ActivityRecord r = mStoppingActivities.get(i); - if (r.getTask() == task) { + if (!r.finishing && r.isState(RESUMED) && r.getTask() == task) { if (activities == null) { activities = new ArrayList<>(); } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 43f32096b6c0..be7b8559e39e 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -429,9 +429,12 @@ class BackNavigationController { final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment(); if (prevTFAdjacent != null) { if (prevTFAdjacent == currTF) { - // Cannot predict what will happen when app receive back key, skip animation. outPrevActivities.clear(); - return false; + // No more activity in task, so it can predict if previous task exists. + // Otherwise, unable to predict what will happen when app receive + // back key, skip animation. + return currentTask.getActivity((below) -> !below.finishing, prevActivity, + false /*includeBoundary*/, true /*traverseTopToBottom*/) == null; } else { final ActivityRecord prevActivityAdjacent = prevTFAdjacent.getTopNonFinishingActivity(); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 8cc197c2f3d0..39e900a97021 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -319,8 +319,6 @@ public class BackgroundActivityStartController { return BackgroundStartPrivileges.NONE; case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED: // no explicit choice by the app - let us decide what to do - Slog.i(TAG, "balRequireOptInByPendingIntentCreator = " - + balRequireOptInByPendingIntentCreator()); if (!balRequireOptInByPendingIntentCreator()) { // if feature is disabled allow return BackgroundStartPrivileges.ALLOW_BAL; @@ -331,7 +329,6 @@ public class BackgroundActivityStartController { DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR, callingPackage, UserHandle.getUserHandleForUid(callingUid)); - Slog.i(TAG, "changeEnabled = " + changeEnabled); return changeEnabled ? BackgroundStartPrivileges.NONE : BackgroundStartPrivileges.ALLOW_BAL; } @@ -340,7 +337,6 @@ public class BackgroundActivityStartController { boolean changeEnabled = CompatChanges.isChangeEnabled( DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR, callingUid); - Slog.i(TAG, "changeEnabled = " + changeEnabled); return changeEnabled ? BackgroundStartPrivileges.NONE : BackgroundStartPrivileges.ALLOW_BAL; default: diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 2c224e458a2d..bb599363bdb5 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -69,8 +69,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; @@ -158,6 +156,8 @@ import static com.android.server.wm.WindowState.EXCLUSION_LEFT; import static com.android.server.wm.WindowState.EXCLUSION_RIGHT; import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP; import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW; +import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS; +import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; import static com.android.server.wm.utils.RegionUtils.forEachRectReverse; import static com.android.server.wm.utils.RegionUtils.rectListToRegion; import static com.android.window.flags.Flags.explicitRefreshRateHints; @@ -465,11 +465,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean mDisplayScalingDisabled; final Display mDisplay; private final DisplayInfo mDisplayInfo = new DisplayInfo(); + + /** + * Contains the last DisplayInfo override that was sent to DisplayManager or null if we haven't + * set an override yet + */ + @Nullable + private DisplayInfo mLastDisplayInfoOverride; + private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); private final DisplayPolicy mDisplayPolicy; private final DisplayRotation mDisplayRotation; @Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; DisplayFrames mDisplayFrames; + private final DisplayUpdater mDisplayUpdater; private boolean mInTouchMode; @@ -623,7 +632,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @VisibleForTesting final DeviceStateController mDeviceStateController; final Consumer<DeviceStateController.DeviceState> mDeviceStateConsumer; - private final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher; + final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher; final RemoteDisplayChangeController mRemoteDisplayChangeController; /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */ @@ -1149,6 +1158,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWallpaperController.resetLargestDisplay(display); display.getDisplayInfo(mDisplayInfo); display.getMetrics(mDisplayMetrics); + mDisplayUpdater = new ImmediateDisplayUpdater(this); mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp * mDisplayMetrics.densityDpi / DENSITY_DEFAULT; isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; @@ -1917,28 +1927,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return true; } - /** Returns {@code true} if the IME is possible to show on the launching activity. */ - boolean mayImeShowOnLaunchingActivity(@NonNull ActivityRecord r) { - final WindowState win = r.findMainWindow(false /* exclude starting window */); - if (win == null) { - return false; - } - // See InputMethodManagerService#shouldRestoreImeVisibility that we expecting the IME - // should be hidden when the window set the hidden softInputMode. - final int softInputMode = win.mAttrs.softInputMode; - switch (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { - case SOFT_INPUT_STATE_ALWAYS_HIDDEN: - case SOFT_INPUT_STATE_HIDDEN: - return false; - } - final boolean useIme = r.getWindow( - w -> WindowManager.LayoutParams.mayUseInputMethod(w.mAttrs.flags)) != null; - if (!useIme) { - return false; - } - return r.mLastImeShown || (r.mStartingData != null && r.mStartingData.hasImeSurface()); - } - /** Returns {@code true} if the top activity is transformed with the new rotation of display. */ boolean hasTopFixedRotationLaunchingApp() { return mFixedRotationLaunchingApp != null @@ -2301,8 +2289,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig); - mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId, - mDisplayInfo); + setDisplayInfoOverride(); if (isDefaultDisplay) { mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics, @@ -2314,6 +2301,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayInfo; } + /** + * Sets the current DisplayInfo in DisplayContent as an override to DisplayManager + */ + private void setDisplayInfoOverride() { + mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId, + mDisplayInfo); + + if (mLastDisplayInfoOverride == null) { + mLastDisplayInfoOverride = new DisplayInfo(); + } + + mLastDisplayInfoOverride.copyFrom(mDisplayInfo); + } + DisplayCutout calculateDisplayCutoutForRotation(int rotation) { return mDisplayCutoutCache.getOrCompute( mIsSizeForced ? mBaseDisplayCutout : mInitialDisplayCutout, rotation) @@ -2885,12 +2886,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return orientation; } - void updateDisplayInfo() { + void updateDisplayInfo(@NonNull DisplayInfo newDisplayInfo) { // Check if display metrics changed and update base values if needed. - updateBaseDisplayMetricsIfNeeded(); + updateBaseDisplayMetricsIfNeeded(newDisplayInfo); - mDisplay.getDisplayInfo(mDisplayInfo); - mDisplay.getMetrics(mDisplayMetrics); + // Update mDisplayInfo with (newDisplayInfo + mLastDisplayInfoOverride) as + // updateBaseDisplayMetricsIfNeeded could have updated mLastDisplayInfoOverride + copyDisplayInfoFields(/* out= */ mDisplayInfo, /* base= */ newDisplayInfo, + /* override= */ mLastDisplayInfoOverride, /* fields= */ WM_OVERRIDE_FIELDS); + mDisplayInfo.getAppMetrics(mDisplayMetrics, mDisplay.getDisplayAdjustments()); onDisplayInfoChanged(); onDisplayChanged(this); @@ -2976,9 +2980,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * If display metrics changed, overrides are not set and it's not just a rotation - update base * values. */ - private void updateBaseDisplayMetricsIfNeeded() { + private void updateBaseDisplayMetricsIfNeeded(DisplayInfo newDisplayInfo) { // Get real display metrics without overrides from WM. - mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo(mDisplayId, mDisplayInfo); + mDisplayInfo.copyFrom(newDisplayInfo); final int currentRotation = getRotation(); final int orientation = mDisplayInfo.rotation; final boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270); @@ -3010,7 +3014,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // metrics are updated as rotation settings might depend on them mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this, /* includeRotationSettings */ false); - mDisplaySwitchTransitionLauncher.requestDisplaySwitchTransitionIfNeeded(mDisplayId, + mDisplayUpdater.onDisplayContentDisplayPropertiesPreChanged(mDisplayId, mInitialDisplayWidth, mInitialDisplayHeight, newWidth, newHeight); mDisplayRotation.physicalDisplayChanged(); mDisplayPolicy.physicalDisplayChanged(); @@ -3046,8 +3050,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (physicalDisplayChanged) { mDisplayPolicy.physicalDisplayUpdated(); - mDisplaySwitchTransitionLauncher.onDisplayUpdated(currentRotation, getRotation(), - getDisplayAreaInfo()); + mDisplayUpdater.onDisplayContentDisplayPropertiesPostChanged(currentRotation, + getRotation(), getDisplayAreaInfo()); } } } @@ -5494,8 +5498,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplayReady = true; if (mWmService.mDisplayManagerInternal != null) { - mWmService.mDisplayManagerInternal - .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo()); + setDisplayInfoOverride(); configureDisplayPolicy(); } @@ -6138,9 +6141,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mMetricsLogger; } - void onDisplayChanged() { + /** + * Triggers an update of DisplayInfo from DisplayManager + * @param onDisplayChangeApplied callback that is called when the changes are applied + */ + void requestDisplayUpdate(@NonNull Runnable onDisplayChangeApplied) { + mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied); + } + + void onDisplayInfoUpdated(@NonNull DisplayInfo newDisplayInfo) { final int lastDisplayState = mDisplayInfo.state; - updateDisplayInfo(); + updateDisplayInfo(newDisplayInfo); // The window policy is responsible for stopping activities on the default display. final int displayId = mDisplay.getDisplayId(); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 708ee7f59726..b862d7c28b52 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1737,11 +1737,12 @@ public class DisplayPolicy { void onOverlayChanged() { updateCurrentUserResources(); // Update the latest display size, cutout. - mDisplayContent.updateDisplayInfo(); - onConfigurationChanged(); - if (!CLIENT_TRANSIENT) { - mSystemGestures.onConfigurationChanged(); - } + mDisplayContent.requestDisplayUpdate(() -> { + onConfigurationChanged(); + if (!CLIENT_TRANSIENT) { + mSystemGestures.onConfigurationChanged(); + } + }); } /** diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index a1b8949c2582..d37661312cfb 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -281,7 +281,7 @@ public class DisplayRotation { mDeskDockRotation = readRotation(R.integer.config_deskDockRotation); mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation); - int defaultRotation = readDefaultDisplayRotation(displayAddress); + int defaultRotation = readDefaultDisplayRotation(displayAddress, displayContent); mRotation = defaultRotation; mDisplayRotationCoordinator = displayRotationCoordinator; @@ -327,22 +327,31 @@ public class DisplayRotation { } // Change the default value to the value specified in the sysprop - // ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0, + // ro.bootanim.set_orientation_<physical_display_id> or + // ro.bootanim.set_orientation_logical_<logical_display_id>. + // Four values are supported: ORIENTATION_0, // ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270. // If the value isn't specified or is ORIENTATION_0, nothing will be changed. // This is needed to support having default orientation different from the natural // device orientation. For example, on tablets that may want to keep natural orientation // portrait for applications compatibility but have landscape orientation as a default choice // from the UX perspective. + // On watches that may want to keep the wrist orientation as the default. @Surface.Rotation - private int readDefaultDisplayRotation(DisplayAddress displayAddress) { - if (!(displayAddress instanceof DisplayAddress.Physical)) { - return Surface.ROTATION_0; - } - final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) displayAddress; - String syspropValue = SystemProperties.get( - "ro.bootanim.set_orientation_" + physicalAddress.getPhysicalDisplayId(), - "ORIENTATION_0"); + private int readDefaultDisplayRotation(DisplayAddress displayAddress, + DisplayContent displayContent) { + String syspropValue = ""; + if (displayAddress instanceof DisplayAddress.Physical) { + final DisplayAddress.Physical physicalAddress = + (DisplayAddress.Physical) displayAddress; + syspropValue = SystemProperties.get( + "ro.bootanim.set_orientation_" + physicalAddress.getPhysicalDisplayId(), ""); + } + if ("".equals(syspropValue) && displayContent.isDefaultDisplay) { + syspropValue = SystemProperties.get( + "ro.bootanim.set_orientation_logical_" + displayContent.getDisplayId(), ""); + } + if (syspropValue.equals("ORIENTATION_90")) { return Surface.ROTATION_90; } else if (syspropValue.equals("ORIENTATION_180")) { diff --git a/services/core/java/com/android/server/wm/DisplayUpdater.java b/services/core/java/com/android/server/wm/DisplayUpdater.java new file mode 100644 index 000000000000..e611177210e8 --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayUpdater.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.annotation.NonNull; +import android.view.Surface; +import android.window.DisplayAreaInfo; + +/** + * Interface for a helper class that manages updates of DisplayInfo coming from DisplayManager + */ +interface DisplayUpdater { + /** + * Reads the latest display parameters from the display manager and returns them in a callback. + * If there are pending display updates, it will wait for them to finish first and only then it + * will call the callback with the latest display parameters. + * + * @param callback is called when all pending display updates are finished + */ + void updateDisplayInfo(@NonNull Runnable callback); + + /** + * Called when physical display has changed and before DisplayContent has applied new display + * properties + */ + default void onDisplayContentDisplayPropertiesPreChanged(int displayId, int initialDisplayWidth, + int initialDisplayHeight, int newWidth, int newHeight) { + } + + /** + * Called after physical display has changed and after DisplayContent applied new display + * properties + */ + default void onDisplayContentDisplayPropertiesPostChanged( + @Surface.Rotation int previousRotation, @Surface.Rotation int newRotation, + @NonNull DisplayAreaInfo newDisplayAreaInfo) { + } +} diff --git a/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java b/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java new file mode 100644 index 000000000000..4af9013d7f4a --- /dev/null +++ b/services/core/java/com/android/server/wm/ImmediateDisplayUpdater.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.annotation.NonNull; +import android.view.DisplayInfo; +import android.window.DisplayAreaInfo; + +/** + * DisplayUpdater that immediately applies new DisplayInfo properties + */ +public class ImmediateDisplayUpdater implements DisplayUpdater { + + private final DisplayContent mDisplayContent; + private final DisplayInfo mDisplayInfo = new DisplayInfo(); + + public ImmediateDisplayUpdater(@NonNull DisplayContent displayContent) { + mDisplayContent = displayContent; + mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo()); + } + + @Override + public void updateDisplayInfo(Runnable callback) { + mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo( + mDisplayContent.mDisplayId, mDisplayInfo); + mDisplayContent.onDisplayInfoUpdated(mDisplayInfo); + callback.run(); + } + + @Override + public void onDisplayContentDisplayPropertiesPreChanged(int displayId, int initialDisplayWidth, + int initialDisplayHeight, int newWidth, int newHeight) { + mDisplayContent.mDisplaySwitchTransitionLauncher.requestDisplaySwitchTransitionIfNeeded( + displayId, initialDisplayWidth, initialDisplayHeight, newWidth, newHeight); + } + + @Override + public void onDisplayContentDisplayPropertiesPostChanged(int previousRotation, int newRotation, + DisplayAreaInfo newDisplayAreaInfo) { + mDisplayContent.mDisplaySwitchTransitionLauncher.onDisplayUpdated(previousRotation, + newRotation, + newDisplayAreaInfo); + } +} diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 0c235bae2006..522e7d205a00 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -627,7 +627,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void refreshSecureSurfaceState() { forAllWindows((w) -> { if (w.mHasSurface) { - w.mWinAnimator.setSecureLocked(w.isSecureLocked()); + w.setSecureLocked(w.isSecureLocked()); } }, true /* traverseTopToBottom */); } @@ -2171,12 +2171,16 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // now, it will take focus briefly which confuses the RecentTasks tracker. rootTask.setWindowingMode(WINDOWING_MODE_PINNED); } - + // Temporarily disable focus when reparenting to avoid intermediate focus change + // (because the task is on top and the activity is resumed), which could cause the + // task to be added in recents task list unexpectedly. + rootTask.setFocusable(false); // There are multiple activities in the task and moving the top activity should // reveal/leave the other activities in their original task. // On the other hand, ActivityRecord#onParentChanged takes care of setting the // up-to-dated root pinned task information on this newly created root task. r.reparent(rootTask, MAX_VALUE, reason); + rootTask.setFocusable(true); // Ensure the leash of new task is in sync with its current bounds after reparent. rootTask.maybeApplyLastRecentsAnimationTransaction(); @@ -2716,15 +2720,20 @@ class RootWindowContainer extends WindowContainer<DisplayContent> synchronized (mService.mGlobalLock) { final DisplayContent displayContent = getDisplayContent(displayId); if (displayContent != null) { - displayContent.onDisplayChanged(); + displayContent.requestDisplayUpdate(() -> clearDisplayInfoCaches(displayId)); + } else { + clearDisplayInfoCaches(displayId); } - // Drop any cached DisplayInfos associated with this display id - the values are now - // out of date given this display changed event. - mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId); - updateDisplayImePolicyCache(); } } + private void clearDisplayInfoCaches(int displayId) { + // Drop any cached DisplayInfos associated with this display id - the values are now + // out of date given this display changed event. + mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId); + updateDisplayImePolicyCache(); + } + void updateDisplayImePolicyCache() { ArrayMap<Integer, Integer> displayImePolicyMap = new ArrayMap<>(); forAllDisplays(dc -> displayImePolicyMap.put(dc.getDisplayId(), dc.getImePolicy())); diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 5c84cb07e891..e7bffdfd448b 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -32,7 +32,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATI import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.utils.CoordinateTransforms.computeRotationMatrix; -import static com.android.window.flags.Flags.removeCaptureDisplay; +import static com.android.window.flags.Flags.deleteCaptureDisplay; import android.animation.ArgbEvaluator; import android.content.Context; @@ -171,7 +171,7 @@ class ScreenRotationAnimation { try { final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer; - if (isSizeChanged && !removeCaptureDisplay()) { + if (isSizeChanged && !deleteCaptureDisplay()) { final DisplayAddress address = displayInfo.address; if (!(address instanceof DisplayAddress.Physical)) { Slog.e(TAG, "Display does not have a physical address: " + displayId); diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 28a35b9ceefe..303211083f29 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -276,12 +276,14 @@ public class StartingSurfaceController { /** * Removes the starting window surface. Do not hold the window manager lock when calling * this method! + * * @param animate Whether need to play the default exit animation for starting window. + * @param hasImeSurface Whether the starting window has IME surface. */ - public void remove(boolean animate) { + public void remove(boolean animate, boolean hasImeSurface) { synchronized (mService.mGlobalLock) { mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, - mTaskOrganizer, animate); + mTaskOrganizer, animate, hasImeSurface); } } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 17ab00d64924..3a711b2c7046 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -673,7 +673,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return true; } - void removeStartingWindow(Task task, ITaskOrganizer taskOrganizer, boolean prepareAnimation) { + void removeStartingWindow(Task task, ITaskOrganizer taskOrganizer, boolean prepareAnimation, + boolean hasImeSurface) { final Task rootTask = task.getRootTask(); if (rootTask == null) { return; @@ -693,13 +694,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { if (topActivity != null) { // Set defer remove mode for IME final DisplayContent dc = topActivity.getDisplayContent(); - final WindowState imeWindow = dc.mInputMethodWindow; - if (topActivity.isVisibleRequested() && imeWindow != null - && dc.mayImeShowOnLaunchingActivity(topActivity) - && dc.isFixedRotationLaunchingApp(topActivity)) { - removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION; - } else if (dc.mayImeShowOnLaunchingActivity(topActivity)) { - removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL; + if (hasImeSurface) { + if (topActivity.isVisibleRequested() && dc.mInputMethodWindow != null + && dc.isFixedRotationLaunchingApp(topActivity)) { + removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION; + } else { + removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL; + } } else { removalInfo.deferRemoveForImeMode = DEFER_MODE_NONE; } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index a736874f178d..bacfda5fc528 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -992,11 +992,19 @@ class TransitionController { private void enforceSurfaceVisible(WindowContainer<?> wc) { if (wc.mSurfaceControl == null) return; wc.getSyncTransaction().show(wc.mSurfaceControl); + final ActivityRecord ar = wc.asActivityRecord(); + if (ar != null) { + ar.mLastSurfaceShowing = true; + } // Force showing the parents because they may be hidden by previous transition. for (WindowContainer<?> p = wc.getParent(); p != null && p != wc.mDisplayContent; p = p.getParent()) { if (p.mSurfaceControl != null) { p.getSyncTransaction().show(p.mSurfaceControl); + final Task task = p.asTask(); + if (task != null) { + task.mLastSurfaceShowing = true; + } } } wc.scheduleAnimation(); diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index fd22f15fb798..750fd509e50f 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -196,7 +196,9 @@ public class WindowAnimator { updateRunningExpensiveAnimationsLegacy(); } + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "applyTransaction"); mTransaction.apply(); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); mService.mWindowTracing.logState("WindowAnimator"); ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate"); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a69a07f9aee0..dd2b48bb5a3d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2327,8 +2327,8 @@ public class WindowManagerService extends IWindowManager.Stub boolean wallpaperMayMove = win.mViewVisibility != viewVisibility && win.hasWallpaper(); wallpaperMayMove |= (flagChanges & FLAG_SHOW_WALLPAPER) != 0; - if ((flagChanges & FLAG_SECURE) != 0 && winAnimator.mSurfaceController != null) { - winAnimator.mSurfaceController.setSecure(win.isSecureLocked()); + if ((flagChanges & FLAG_SECURE) != 0) { + win.setSecureLocked(win.isSecureLocked()); } final boolean wasVisible = win.isVisible(); @@ -6238,9 +6238,11 @@ public class WindowManagerService extends IWindowManager.Stub return; } - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay"); - doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation); - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + displayContent.requestDisplayUpdate(() -> { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay"); + doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + }); } private void doStartFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent, @@ -6276,7 +6278,6 @@ public class WindowManagerService extends IWindowManager.Stub mExitAnimId = exitAnim; mEnterAnimId = enterAnim; - displayContent.updateDisplayInfo(); final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED ? overrideOriginalRotation : displayContent.getDisplayInfo().rotation; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 3e43908994ad..e1f1f662c5aa 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -109,6 +109,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RESIZE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS; +import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT; @@ -177,6 +178,7 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY; import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES; +import static com.android.window.flags.Flags.secureWindowState; import static com.android.window.flags.Flags.surfaceTrustedOverlay; import android.annotation.CallSuper; @@ -1195,6 +1197,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) { getPendingTransaction().setTrustedOverlay(mSurfaceControl, true); } + if (secureWindowState()) { + getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked()); + } } void updateTrustedOverlay() { @@ -3276,7 +3281,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // just kill it. And if it is a window of foreground activity, the activity can be // restarted automatically if needed. Slog.w(TAG, "Exception thrown during dispatchAppVisibility " + this, e); - android.os.Process.killProcess(mSession.mPid); + if (android.os.Process.getUidForPid(mSession.mPid) == mSession.mUid) { + android.os.Process.killProcess(mSession.mPid); + } } } @@ -6040,4 +6047,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Cancel any draw requests during a sync. return mPrepareSyncSeqId > 0; } + + void setSecureLocked(boolean isSecure) { + ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, getName()); + if (secureWindowState()) { + if (mSurfaceControl == null) { + return; + } + getPendingTransaction().setSecure(mSurfaceControl, isSecure); + } else { + if (mWinAnimator.mSurfaceController == null + || mWinAnimator.mSurfaceController.mSurfaceControl == null) { + return; + } + getPendingTransaction().setSecure(mWinAnimator.mSurfaceController.mSurfaceControl, + isSecure); + } + if (mDisplayContent != null) { + mDisplayContent.refreshImeSecureFlag(getSyncTransaction()); + } + mWmService.scheduleAnimationLocked(); + } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 3aac816fcd7a..44cd23d037c6 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -44,6 +44,7 @@ import static com.android.server.wm.WindowManagerService.logWithStack; import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE; import static com.android.server.wm.WindowStateAnimatorProto.SURFACE; import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT; +import static com.android.window.flags.Flags.secureWindowState; import android.content.Context; import android.graphics.PixelFormat; @@ -286,8 +287,10 @@ class WindowStateAnimator { int flags = SurfaceControl.HIDDEN; final WindowManager.LayoutParams attrs = w.mAttrs; - if (w.isSecureLocked()) { - flags |= SurfaceControl.SECURE; + if (!secureWindowState()) { + if (w.isSecureLocked()) { + flags |= SurfaceControl.SECURE; + } } if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) { @@ -488,13 +491,6 @@ class WindowStateAnimator { mSurfaceController.setOpaque(isOpaque); } - void setSecureLocked(boolean isSecure) { - if (mSurfaceController == null) { - return; - } - mSurfaceController.setSecure(isSecure); - } - void setColorSpaceAgnosticLocked(boolean agnostic) { if (mSurfaceController == null) { return; diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index d348491b3d2a..4456a94ef510 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -24,7 +24,6 @@ import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; -import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN; @@ -152,24 +151,6 @@ class WindowSurfaceController { mService.scheduleAnimationLocked(); } - void setSecure(boolean isSecure) { - ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, title); - - if (mSurfaceControl == null) { - return; - } - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setSecureLocked"); - - final SurfaceControl.Transaction t = mAnimator.mWin.getPendingTransaction(); - t.setSecure(mSurfaceControl, isSecure); - - final DisplayContent dc = mAnimator.mWin.mDisplayContent; - if (dc != null) { - dc.refreshImeSecureFlag(t); - } - mService.scheduleAnimationLocked(); - } - void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) { ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title); diff --git a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java new file mode 100644 index 000000000000..8c8f6a6cb386 --- /dev/null +++ b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.utils; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.view.DisplayInfo; + +/** + * Helper class to copy only subset of fields of DisplayInfo object or to perform + * comparison operation between DisplayInfo objects only with a subset of fields. + */ +public class DisplayInfoOverrides { + + /** + * Set of DisplayInfo fields that are overridden in DisplayManager using values from + * WindowManager + */ + public static final DisplayInfoFields WM_OVERRIDE_FIELDS = (out, source) -> { + out.appWidth = source.appWidth; + out.appHeight = source.appHeight; + out.smallestNominalAppWidth = source.smallestNominalAppWidth; + out.smallestNominalAppHeight = source.smallestNominalAppHeight; + out.largestNominalAppWidth = source.largestNominalAppWidth; + out.largestNominalAppHeight = source.largestNominalAppHeight; + out.logicalWidth = source.logicalWidth; + out.logicalHeight = source.logicalHeight; + out.physicalXDpi = source.physicalXDpi; + out.physicalYDpi = source.physicalYDpi; + out.rotation = source.rotation; + out.displayCutout = source.displayCutout; + out.logicalDensityDpi = source.logicalDensityDpi; + out.roundedCorners = source.roundedCorners; + out.displayShape = source.displayShape; + }; + + /** + * Gets {@param base} DisplayInfo, overrides WindowManager-specific overrides using + * {@param override} and writes the result to {@param out} + */ + public static void copyDisplayInfoFields(@NonNull DisplayInfo out, + @NonNull DisplayInfo base, + @Nullable DisplayInfo override, + @NonNull DisplayInfoFields fields) { + out.copyFrom(base); + + if (override != null) { + fields.setFields(out, override); + } + } + + /** + * Callback interface that allows to specify a subset of fields of DisplayInfo object + */ + public interface DisplayInfoFields { + /** + * Copies a subset of fields from {@param source} to {@param out} + * + * @param out resulting DisplayInfo object + * @param source source DisplayInfo to copy fields from + */ + void setFields(@NonNull DisplayInfo out, @NonNull DisplayInfo source); + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 9b62a2c41655..e0a2f30b1831 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -18182,7 +18182,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static boolean hasAccountFeatures(AccountManager am, Account account, String[] features) { try { - return am.hasFeatures(account, features, null, null).getResult(); + return am.hasFeatures(account, features, null, null) + .getResult(30, TimeUnit.SECONDS); } catch (Exception e) { Slogf.w(LOG_TAG, "Failed to get account feature", e); return false; diff --git a/services/foldables/devicestateprovider/Android.bp b/services/foldables/devicestateprovider/Android.bp index 34737eff8e6d..56daea772cfd 100644 --- a/services/foldables/devicestateprovider/Android.bp +++ b/services/foldables/devicestateprovider/Android.bp @@ -5,9 +5,12 @@ package { java_library { name: "foldable-device-state-provider", srcs: [ - "src/**/*.java" + "src/**/*.java", ], libs: [ "services", ], + static_libs: [ + "device_state_flags_lib", + ], } diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java index aea46d1ce329..4c487a70390d 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java @@ -21,6 +21,7 @@ import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STA import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.TYPE_EXTERNAL; import android.annotation.IntRange; import android.annotation.NonNull; @@ -33,11 +34,14 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.os.PowerManager; import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; import android.os.Trace; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.Display; import com.android.internal.annotations.GuardedBy; @@ -45,24 +49,26 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.server.devicestate.DeviceState; import com.android.server.devicestate.DeviceStateProvider; +import com.android.server.policy.feature.flags.FeatureFlags; +import com.android.server.policy.feature.flags.FeatureFlagsImpl; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.function.BooleanSupplier; -import java.util.function.Function; +import java.util.function.Predicate; /** * Device state provider for foldable devices. - * + * <p> * It is an implementation of {@link DeviceStateProvider} tailored specifically for * foldable devices and allows simple callback-based configuration with hall sensor * and hinge angle sensor values. */ public final class FoldableDeviceStateProvider implements DeviceStateProvider, SensorEventListener, PowerManager.OnThermalStatusChangedListener, - DisplayManager.DisplayListener { + DisplayManager.DisplayListener { private static final String TAG = "FoldableDeviceStateProvider"; private static final boolean DEBUG = false; @@ -77,9 +83,17 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, // are met for the device to be in the state. private final SparseArray<BooleanSupplier> mStateConditions = new SparseArray<>(); + // Map of state identifier to a boolean supplier that returns true when the device state has all + // the conditions needed for availability. + private final SparseArray<BooleanSupplier> mStateAvailabilityConditions = new SparseArray<>(); + + @GuardedBy("mLock") + private final SparseBooleanArray mExternalDisplaysConnected = new SparseBooleanArray(); + private final Sensor mHingeAngleSensor; private final DisplayManager mDisplayManager; private final Sensor mHallSensor; + private static final Predicate<FoldableDeviceStateProvider> ALLOWED = p -> true; @Nullable @GuardedBy("mLock") @@ -99,7 +113,23 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, @GuardedBy("mLock") private boolean mPowerSaveModeEnabled; - public FoldableDeviceStateProvider(@NonNull Context context, + private final boolean mIsDualDisplayBlockingEnabled; + + public FoldableDeviceStateProvider( + @NonNull Context context, + @NonNull SensorManager sensorManager, + @NonNull Sensor hingeAngleSensor, + @NonNull Sensor hallSensor, + @NonNull DisplayManager displayManager, + @NonNull DeviceStateConfiguration[] deviceStateConfigurations) { + this(new FeatureFlagsImpl(), context, sensorManager, hingeAngleSensor, hallSensor, + displayManager, deviceStateConfigurations); + } + + @VisibleForTesting + public FoldableDeviceStateProvider( + @NonNull FeatureFlags featureFlags, + @NonNull Context context, @NonNull SensorManager sensorManager, @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, @@ -112,6 +142,7 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, mHingeAngleSensor = hingeAngleSensor; mHallSensor = hallSensor; mDisplayManager = displayManager; + mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking(); sensorManager.registerListener(this, mHingeAngleSensor, SENSOR_DELAY_FASTEST); sensorManager.registerListener(this, mHallSensor, SENSOR_DELAY_FASTEST); @@ -121,20 +152,15 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, final DeviceStateConfiguration configuration = deviceStateConfigurations[i]; mOrderedStates[i] = configuration.mDeviceState; - if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) { - throw new IllegalArgumentException("Device state configurations must have unique" - + " device state identifiers, found duplicated identifier: " + - configuration.mDeviceState.getIdentifier()); - } - - mStateConditions.put(configuration.mDeviceState.getIdentifier(), () -> - configuration.mPredicate.apply(this)); + assertUniqueDeviceStateIdentifier(configuration); + initialiseStateConditions(configuration); + initialiseStateAvailabilityConditions(configuration); } + Handler handler = new Handler(Looper.getMainLooper()); mDisplayManager.registerDisplayListener( /* listener = */ this, - /* handler= */ null, - /* eventsMask= */ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); + /* handler= */ handler); Arrays.sort(mOrderedStates, Comparator.comparingInt(DeviceState::getIdentifier)); @@ -167,6 +193,24 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, } } + private void assertUniqueDeviceStateIdentifier(DeviceStateConfiguration configuration) { + if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) { + throw new IllegalArgumentException("Device state configurations must have unique" + + " device state identifiers, found duplicated identifier: " + + configuration.mDeviceState.getIdentifier()); + } + } + + private void initialiseStateConditions(DeviceStateConfiguration configuration) { + mStateConditions.put(configuration.mDeviceState.getIdentifier(), () -> + configuration.mActiveStatePredicate.test(this)); + } + + private void initialiseStateAvailabilityConditions(DeviceStateConfiguration configuration) { + mStateAvailabilityConditions.put(configuration.mDeviceState.getIdentifier(), () -> + configuration.mAvailabilityPredicate.test(this)); + } + @Override public void setListener(Listener listener) { synchronized (mLock) { @@ -189,16 +233,9 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, } listener = mListener; for (DeviceState deviceState : mOrderedStates) { - if (isThermalStatusCriticalOrAbove(mThermalStatus) - && deviceState.hasFlag( - DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) { - continue; - } - if (mPowerSaveModeEnabled && deviceState.hasFlag( - DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) { - continue; + if (isStateSupported(deviceState)) { + supportedStates.add(deviceState); } - supportedStates.add(deviceState); } } @@ -206,6 +243,26 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, supportedStates.toArray(new DeviceState[supportedStates.size()]), reason); } + @GuardedBy("mLock") + private boolean isStateSupported(DeviceState deviceState) { + if (isThermalStatusCriticalOrAbove(mThermalStatus) + && deviceState.hasFlag( + DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) { + return false; + } + if (mPowerSaveModeEnabled && deviceState.hasFlag( + DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) { + return false; + } + if (mIsDualDisplayBlockingEnabled + && mStateAvailabilityConditions.contains(deviceState.getIdentifier())) { + return mStateAvailabilityConditions + .get(deviceState.getIdentifier()) + .getAsBoolean(); + } + return true; + } + /** Computes the current device state and notifies the listener of a change, if needed. */ void notifyDeviceStateChangedIfNeeded() { int stateToReport = INVALID_DEVICE_STATE; @@ -294,7 +351,7 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, private void dumpSensorValues() { Slog.i(TAG, "Sensor values:"); dumpSensorValues("Hall Sensor", mHallSensor, mLastHallSensorEvent); - dumpSensorValues("Hinge Angle Sensor",mHingeAngleSensor, mLastHingeAngleSensorEvent); + dumpSensorValues("Hinge Angle Sensor", mHingeAngleSensor, mLastHingeAngleSensorEvent); Slog.i(TAG, "isScreenOn: " + isScreenOn()); } @@ -307,12 +364,35 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, @Override public void onDisplayAdded(int displayId) { - + // TODO(b/312397262): consider virtual displays cases + synchronized (mLock) { + if (mIsDualDisplayBlockingEnabled + && !mExternalDisplaysConnected.get(displayId, false) + && mDisplayManager.getDisplay(displayId).getType() == TYPE_EXTERNAL) { + mExternalDisplaysConnected.put(displayId, true); + + // Only update the supported state when going from 0 external display to 1 + if (mExternalDisplaysConnected.size() == 1) { + notifySupportedStatesChanged( + SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED); + } + } + } } @Override public void onDisplayRemoved(int displayId) { + synchronized (mLock) { + if (mIsDualDisplayBlockingEnabled && mExternalDisplaysConnected.get(displayId, false)) { + mExternalDisplaysConnected.delete(displayId); + // Only update the supported states when going from 1 external display to 0 + if (mExternalDisplaysConnected.size() == 0) { + notifySupportedStatesChanged( + SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED); + } + } + } } @Override @@ -338,48 +418,71 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, */ public static class DeviceStateConfiguration { private final DeviceState mDeviceState; - private final Function<FoldableDeviceStateProvider, Boolean> mPredicate; + private final Predicate<FoldableDeviceStateProvider> mActiveStatePredicate; + private final Predicate<FoldableDeviceStateProvider> mAvailabilityPredicate; + + private DeviceStateConfiguration( + @NonNull DeviceState deviceState, + @NonNull Predicate<FoldableDeviceStateProvider> predicate) { + this(deviceState, predicate, ALLOWED); + } + + private DeviceStateConfiguration( + @NonNull DeviceState deviceState, + @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate, + @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate) { - private DeviceStateConfiguration(DeviceState deviceState, - Function<FoldableDeviceStateProvider, Boolean> predicate) { mDeviceState = deviceState; - mPredicate = predicate; + mActiveStatePredicate = activeStatePredicate; + mAvailabilityPredicate = availabilityPredicate; } public static DeviceStateConfiguration createConfig( @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier, @NonNull String name, @DeviceState.DeviceStateFlags int flags, - Function<FoldableDeviceStateProvider, Boolean> predicate + @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate ) { return new DeviceStateConfiguration(new DeviceState(identifier, name, flags), - predicate); + activeStatePredicate); } public static DeviceStateConfiguration createConfig( @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier, @NonNull String name, - Function<FoldableDeviceStateProvider, Boolean> predicate + @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate ) { return new DeviceStateConfiguration(new DeviceState(identifier, name, /* flags= */ 0), - predicate); + activeStatePredicate); + } + + /** Create a configuration with availability predicate **/ + public static DeviceStateConfiguration createConfig( + @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier, + @NonNull String name, + @DeviceState.DeviceStateFlags int flags, + @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate, + @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate + ) { + return new DeviceStateConfiguration(new DeviceState(identifier, name, flags), + activeStatePredicate, availabilityPredicate); } /** * Creates a device state configuration for a closed tent-mode aware state. - * + * <p> * During tent mode: * - The inner display is OFF * - The outer display is ON * - The device is partially unfolded (left and right edges could be on the table) * In this mode the device the device so it could be used in a posture where both left * and right edges of the unfolded device are on the table. - * + * <p> * The predicate returns false after the hinge angle reaches * {@code tentModeSwitchAngleDegrees}. Then it switches back only when the hinge angle * becomes less than {@code maxClosedAngleDegrees}. Hinge angle is 0 degrees when the device * is fully closed and 180 degrees when it is fully unfolded. - * + * <p> * For example, when tentModeSwitchAngleDegrees = 90 and maxClosedAngleDegrees = 5 degrees: * - when unfolding the device from fully closed posture (last state == closed or it is * undefined yet) this state will become not matching after reaching the angle @@ -435,6 +538,15 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, } /** + * @return Whether there is an external connected display. + */ + public boolean hasNoConnectedExternalDisplay() { + synchronized (mLock) { + return mExternalDisplaysConnected.size() == 0; + } + } + + /** * @return Whether the screen is on. */ public boolean isScreenOn() { @@ -442,6 +554,7 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, return mIsScreenOn; } } + /** * @return current hinge angle value of a foldable device */ diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java index 5f2cf3cb5060..5968b6346d35 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java @@ -33,6 +33,10 @@ import android.hardware.display.DisplayManager; import com.android.server.devicestate.DeviceStatePolicy; import com.android.server.devicestate.DeviceStateProvider; import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration; +import com.android.server.policy.feature.flags.FeatureFlags; +import com.android.server.policy.feature.flags.FeatureFlagsImpl; + +import java.util.function.Predicate; /** * Device state policy for a foldable device that supports tent mode: a mode when the device @@ -55,6 +59,10 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { private final DeviceStateProvider mProvider; + private final boolean mIsDualDisplayBlockingEnabled; + private static final Predicate<FoldableDeviceStateProvider> ALLOWED = p -> true; + private static final Predicate<FoldableDeviceStateProvider> NOT_ALLOWED = p -> false; + /** * Creates TentModeDeviceStatePolicy * @@ -67,6 +75,12 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { */ public TentModeDeviceStatePolicy(@NonNull Context context, @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, int closeAngleDegrees) { + this(new FeatureFlagsImpl(), context, hingeAngleSensor, hallSensor, closeAngleDegrees); + } + + public TentModeDeviceStatePolicy(@NonNull FeatureFlags featureFlags, @NonNull Context context, + @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, + int closeAngleDegrees) { super(context); final SensorManager sensorManager = mContext.getSystemService(SensorManager.class); @@ -74,8 +88,10 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { final DeviceStateConfiguration[] configuration = createConfiguration(closeAngleDegrees); - mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor, - hallSensor, displayManager, configuration); + mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking(); + + mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, + hingeAngleSensor, hallSensor, displayManager, configuration); } private DeviceStateConfiguration[] createConfiguration(int closeAngleDegrees) { @@ -83,24 +99,27 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { createClosedConfiguration(closeAngleDegrees), createConfig(DEVICE_STATE_HALF_OPENED, /* name= */ "HALF_OPENED", - (provider) -> { + /* activeStatePredicate= */ (provider) -> { final float hingeAngle = provider.getHingeAngle(); return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES && hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES; }), createConfig(DEVICE_STATE_OPENED, /* name= */ "OPENED", - (provider) -> true), + /* activeStatePredicate= */ ALLOWED), createConfig(DEVICE_STATE_REAR_DISPLAY_STATE, /* name= */ "REAR_DISPLAY_STATE", /* flags= */ FLAG_EMULATED_ONLY, - (provider) -> false), + /* activeStatePredicate= */ NOT_ALLOWED), createConfig(DEVICE_STATE_CONCURRENT_INNER_DEFAULT, /* name= */ "CONCURRENT_INNER_DEFAULT", /* flags= */ FLAG_EMULATED_ONLY | FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP | FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL | FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE, - (provider) -> false) + /* activeStatePredicate= */ NOT_ALLOWED, + /* availabilityPredicate= */ + provider -> !mIsDualDisplayBlockingEnabled + || provider.hasNoConnectedExternalDisplay()) }; } @@ -111,7 +130,7 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { DEVICE_STATE_CLOSED, /* name= */ "CLOSED", /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS, - (provider) -> { + /* activeStatePredicate= */ (provider) -> { final float hingeAngle = provider.getHingeAngle(); return hingeAngle <= closeAngleDegrees; } diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp new file mode 100644 index 000000000000..6ad8d790485c --- /dev/null +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp @@ -0,0 +1,12 @@ +aconfig_declarations { + name: "device_state_flags", + package: "com.android.server.policy.feature.flags", + srcs: [ + "device_state_flags.aconfig", + ], +} + +java_aconfig_library { + name: "device_state_flags_lib", + aconfig_declarations: "device_state_flags", +} diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig new file mode 100644 index 000000000000..47c2a1b079f8 --- /dev/null +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.policy.feature.flags" + +flag { + name: "enable_dual_display_blocking" + namespace: "display_manager" + description: "Feature flag for dual display blocking" + bug: "278667199" +}
\ No newline at end of file diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java index 8fa4ce592777..ddf4a089e76e 100644 --- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java +++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java @@ -17,18 +17,21 @@ package com.android.server.policy; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.STATE_OFF; +import static android.view.Display.STATE_ON; +import static android.view.Display.TYPE_EXTERNAL; +import static android.view.Display.TYPE_INTERNAL; + +import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED; +import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL; -import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration; - -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.Display.STATE_OFF; -import static android.view.Display.STATE_ON; - import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -36,12 +39,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.nullable; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -51,20 +53,21 @@ import android.hardware.SensorEvent; import android.hardware.SensorManager; import android.hardware.display.DisplayManager; import android.hardware.input.InputSensorInfo; -import android.os.PowerManager; import android.os.Handler; +import android.os.PowerManager; import android.testing.AndroidTestingRunner; import android.view.Display; import com.android.server.devicestate.DeviceState; -import com.android.server.devicestate.DeviceStateProvider; import com.android.server.devicestate.DeviceStateProvider.Listener; +import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration; +import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl; +import com.android.server.policy.feature.flags.Flags; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -95,10 +98,16 @@ public final class FoldableDeviceStateProviderTest { @Mock private DisplayManager mDisplayManager; private FoldableDeviceStateProvider mProvider; + @Mock + private Display mDefaultDisplay; + @Mock + private Display mExternalDisplay; + private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl(); @Before public void setup() { MockitoAnnotations.initMocks(this); + mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true); mHallSensor = new Sensor(mInputSensorInfo); mHingeAngleSensor = new Sensor(mInputSensorInfo); @@ -473,6 +482,133 @@ public final class FoldableDeviceStateProviderTest { assertThat(mProvider.isScreenOn()).isFalse(); } + @Test + public void test_dualScreenDisabledWhenExternalScreenIsConnected() throws Exception { + when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay}); + when(mDefaultDisplay.getType()).thenReturn(TYPE_INTERNAL); + + createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED", + (c) -> c.getHingeAngle() < 5f), + createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED", + (c) -> c.getHingeAngle() < 90f), + createConfig(/* identifier= */ 3, /* name= */ "OPENED", + (c) -> c.getHingeAngle() < 180f), + createConfig(/* identifier= */ 4, /* name= */ "DUAL_DISPLAY", /* flags */ 0, + (c) -> false, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)); + + Listener listener = mock(Listener.class); + mProvider.setListener(listener); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED)); + assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder(); + + clearInvocations(listener); + + when(mDisplayManager.getDisplays()) + .thenReturn(new Display[]{mDefaultDisplay, mExternalDisplay}); + when(mDisplayManager.getDisplay(1)).thenReturn(mExternalDisplay); + when(mExternalDisplay.getType()).thenReturn(TYPE_EXTERNAL); + + // The DUAL_DISPLAY state should be disabled. + mProvider.onDisplayAdded(1); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED)); + assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */)}).inOrder(); + clearInvocations(listener); + + // The DUAL_DISPLAY state should be re-enabled. + when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay}); + mProvider.onDisplayRemoved(1); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED)); + assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder(); + } + + @Test + public void test_notifySupportedStatesChangedCalledOnlyOnInitialExternalScreenAddition() { + when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay}); + when(mDefaultDisplay.getType()).thenReturn(TYPE_INTERNAL); + + createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED", + (c) -> c.getHingeAngle() < 5f), + createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED", + (c) -> c.getHingeAngle() < 90f), + createConfig(/* identifier= */ 3, /* name= */ "OPENED", + (c) -> c.getHingeAngle() < 180f), + createConfig(/* identifier= */ 4, /* name= */ "DUAL_DISPLAY", /* flags */ 0, + (c) -> false, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)); + + Listener listener = mock(Listener.class); + mProvider.setListener(listener); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED)); + assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder(); + + clearInvocations(listener); + + addExternalDisplay(1); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED)); + addExternalDisplay(2); + addExternalDisplay(3); + addExternalDisplay(4); + verify(listener, times(1)) + .onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED)); + } + + @Test + public void hasNoConnectedDisplay_afterExternalDisplayAdded_returnsFalse() { + createProvider( + createConfig( + /* identifier= */ 1, /* name= */ "ONE", + /* flags= */0, (c) -> true, + FoldableDeviceStateProvider::hasNoConnectedExternalDisplay) + ); + + addExternalDisplay(/* displayId */ 1); + + assertThat(mProvider.hasNoConnectedExternalDisplay()).isFalse(); + } + + @Test + public void hasNoConnectedDisplay_afterExternalDisplayAddedAndRemoved_returnsTrue() { + createProvider( + createConfig( + /* identifier= */ 1, /* name= */ "ONE", + /* flags= */0, (c) -> true, + FoldableDeviceStateProvider::hasNoConnectedExternalDisplay) + ); + + addExternalDisplay(/* displayId */ 1); + mProvider.onDisplayRemoved(1); + + assertThat(mProvider.hasNoConnectedExternalDisplay()).isTrue(); + } + private void addExternalDisplay(int displayId) { + when(mDisplayManager.getDisplay(displayId)).thenReturn(mExternalDisplay); + when(mExternalDisplay.getType()).thenReturn(TYPE_EXTERNAL); + mProvider.onDisplayAdded(displayId); + } private void setScreenOn(boolean isOn) { Display mockDisplay = mock(Display.class); int state = isOn ? STATE_ON : STATE_OFF; @@ -508,12 +644,11 @@ public final class FoldableDeviceStateProviderTest { } private void createProvider(DeviceStateConfiguration... configurations) { - mProvider = new FoldableDeviceStateProvider(mContext, mSensorManager, mHingeAngleSensor, - mHallSensor, mDisplayManager, configurations); + mProvider = new FoldableDeviceStateProvider(mFakeFeatureFlags, mContext, mSensorManager, + mHingeAngleSensor, mHallSensor, mDisplayManager, configurations); verify(mDisplayManager) .registerDisplayListener( mDisplayListenerCaptor.capture(), - nullable(Handler.class), - anyLong()); + nullable(Handler.class)); } } diff --git a/services/permission/OWNERS b/services/permission/OWNERS index e464038e68d9..487c992bbab4 100644 --- a/services/permission/OWNERS +++ b/services/permission/OWNERS @@ -1,5 +1,3 @@ #Bug component: 137825 -joecastro@google.com -ntmyren@google.com -zhanghai@google.com +include platform/frameworks/base:/core/java/android/permission/OWNERS diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index f94a0d664a69..8f464d41792d 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -71,7 +71,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS // Not implemented because upgrades are handled automatically. } - override fun getNonDefaultUidModes(uid: Int): SparseIntArray { + override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray { return opNameMapToOpSparseArray(getUidModes(uid)) } @@ -79,7 +79,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS return opNameMapToOpSparseArray(getPackageModes(packageName, userId)) } - override fun getUidMode(uid: Int, op: Int): Int { + override fun getUidMode(uid: Int, persistentDeviceId: String, op: Int): Int { val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) @@ -92,7 +92,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map } - override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean { + override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean { val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) @@ -150,7 +150,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS // and we have our own persistence. } - override fun getForegroundOps(uid: Int): SparseBooleanArray { + override fun getForegroundOps(uid: Int, persistentDeviceId: String): SparseBooleanArray { return SparseBooleanArray().apply { getUidModes(uid)?.forEachIndexed { _, op, mode -> if (mode == AppOpsManager.MODE_FOREGROUND) { diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java index 4e468362177a..d62da1a02525 100644 --- a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java +++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java @@ -57,11 +57,11 @@ import android.platform.test.annotations.Presubmit; import androidx.test.core.app.ApplicationProvider; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.FunctionalUtils.ThrowingSupplier; import com.android.server.LocalServices; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.testing.shadows.ShadowApplicationPackageManager; import com.android.server.testing.shadows.ShadowUserManager; diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index 8d76fddcc793..3011fa17d4fc 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -24,6 +24,8 @@ import android.content.pm.PackageManager import android.os.Binder import android.os.UserHandle import android.util.ArrayMap +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal +import com.android.internal.pm.parsing.pkg.ParsedPackage import com.android.internal.pm.pkg.component.ParsedActivity import com.android.server.pm.AppsFilterImpl import com.android.server.pm.PackageManagerService @@ -36,9 +38,7 @@ import com.android.server.pm.Settings import com.android.server.pm.SharedLibrariesImpl import com.android.server.pm.UserManagerInternal import com.android.server.pm.UserManagerService -import com.android.server.pm.parsing.pkg.AndroidPackageInternal import com.android.server.pm.parsing.pkg.PackageImpl -import com.android.server.pm.parsing.pkg.ParsedPackage import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.resolution.ComponentResolver import com.android.server.pm.snapshot.PackageDataSnapshot diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java index 25146a87970f..3461bb6b2c55 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java @@ -49,11 +49,12 @@ import android.util.SparseArray; import androidx.annotation.NonNull; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.om.OverlayReferenceMapper; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.component.ParsedActivityImpl; import com.android.server.pm.pkg.component.ParsedComponentImpl; @@ -62,7 +63,6 @@ import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; import com.android.server.pm.pkg.component.ParsedPermissionImpl; import com.android.server.pm.pkg.component.ParsedProviderImpl; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; -import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.utils.WatchableTester; import org.junit.Before; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index 2810145c08a9..a0dc2b68415c 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -63,10 +63,10 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.permission.persistence.RuntimePermissionsPersistence; import com.android.server.LocalServices; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.ArchiveState; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java index 7552800d8b0e..9c48af8ecd01 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java @@ -72,7 +72,7 @@ import androidx.test.filters.Suppress; import com.android.compatibility.common.util.CddTest; import com.android.internal.content.InstallLocationUtils; -import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.test.service.server.R; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index 7c28e13f0eee..ea88ec25b26e 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -58,6 +58,7 @@ import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.pm.pkg.component.ParsedComponent; @@ -68,6 +69,7 @@ import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.util.ArrayUtils; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageInfoUtils; @@ -75,7 +77,6 @@ import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageUserStateInternal; @@ -88,7 +89,6 @@ import com.android.server.pm.pkg.component.ParsedPermissionUtils; import com.android.server.pm.pkg.component.ParsedProviderImpl; import com.android.server.pm.pkg.component.ParsedServiceImpl; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; -import com.android.server.pm.pkg.parsing.ParsingPackage; import org.junit.Before; import org.junit.Rule; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java index 38d01d0c4c18..8a74e24a3810 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java @@ -21,9 +21,9 @@ import android.util.Log; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.TestPackageParser2; -import com.android.server.pm.parsing.pkg.ParsedPackage; import junit.framework.Assert; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java index 1c3673e84038..2a8e5b18bda3 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.UserHandle; -import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; class ScanRequestBuilder { diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java index e2939c1aff3b..decb44c2cd9b 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java @@ -50,13 +50,13 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.Pair; +import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.compat.PlatformCompat; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; -import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import org.hamcrest.BaseMatcher; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index 7123c2076640..b102ab4f7e3b 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -38,12 +38,12 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.util.ArrayUtils; import com.android.server.pm.PackageManagerException; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.component.ParsedActivityUtils; import com.android.server.pm.pkg.component.ParsedPermissionUtils; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt index 3b926c266b16..67b91d2646d9 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt @@ -18,12 +18,11 @@ package com.android.server.pm.parsing import android.annotation.RawRes import android.content.Context -import com.android.server.pm.pkg.parsing.ParsingPackage -import com.android.server.pm.pkg.parsing.ParsingPackageUtils import android.content.pm.parsing.result.ParseResult import android.platform.test.annotations.Presubmit import androidx.test.InstrumentationRegistry -import com.android.server.pm.parsing.pkg.ParsedPackage +import com.android.internal.pm.parsing.pkg.ParsedPackage +import com.android.server.pm.pkg.parsing.ParsingPackageUtils import com.android.server.pm.test.service.server.R import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java index f376e7397042..6cd71237efa2 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java @@ -24,8 +24,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java index 9248da65e330..27fd781584f2 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java index 23a2c20c8fa2..b13d6de55cf6 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java @@ -23,8 +23,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java index 2060caaac2b5..fa69f844c33a 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java @@ -24,9 +24,9 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.library.PackageBackwardCompatibility.AndroidTestRunnerSplitUpdater; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java index b3ad8616b112..856013a96017 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java @@ -22,9 +22,9 @@ import android.util.ArrayMap; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.SystemConfig; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Before; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java index 558c0e8b4736..ae5ea21aec4e 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java index 7a2ac75ab0d6..e126ffcab5ff 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java @@ -23,8 +23,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java index c4b8e6f445c5..d0b0cf894340 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java @@ -28,11 +28,11 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.parsing.ParsingPackage; import org.junit.Assume; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java index 33fc261d764f..d60c457a10b3 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java @@ -18,7 +18,7 @@ package com.android.server.pm.parsing.library; import static org.junit.Assert.assertEquals; -import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import java.util.function.Supplier; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java index 89182333cb16..c141c0337540 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java @@ -23,9 +23,9 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java index 3e9ec0e3e4e5..a58604b81e06 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java @@ -23,9 +23,9 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryOrgApacheHttpLegacyLibrary; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt index 766ab94e8587..9341e9d96335 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt @@ -21,9 +21,9 @@ import android.content.pm.ApplicationInfo import android.os.Build import android.os.PatternMatcher import android.util.ArraySet +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal import com.android.server.SystemConfig import com.android.server.compat.PlatformCompat -import com.android.server.pm.parsing.pkg.AndroidPackageInternal import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.component.ParsedActivityImpl import com.android.server.pm.pkg.component.ParsedIntentInfoImpl diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 9fbf86e36df4..a737b9097b53 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -28,9 +28,8 @@ import android.util.ArraySet import android.util.IndentingPrintWriter import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal import com.android.server.pm.Computer -import com.android.server.pm.parsing.pkg.AndroidPackageInternal -import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal import com.android.server.pm.pkg.component.ParsedActivityImpl diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt index 47d9196b502b..f38df22af630 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt @@ -29,7 +29,7 @@ import android.os.PatternMatcher import android.os.Process import android.util.ArraySet import android.util.SparseArray -import com.android.server.pm.parsing.pkg.AndroidPackageInternal +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal import com.android.server.pm.pkg.component.ParsedActivityImpl diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt index 98d780143ff7..874e0d2bbc9a 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -36,9 +36,8 @@ import android.os.Process import android.util.ArraySet import android.util.SparseArray import android.util.Xml +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal import com.android.server.pm.Computer -import com.android.server.pm.parsing.pkg.AndroidPackageInternal -import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal import com.android.server.pm.pkg.component.ParsedActivityImpl diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index 4a211dfeb91e..3207e6c2a411 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -24,7 +24,7 @@ import android.os.Build import android.os.Process import android.util.ArraySet import android.util.SparseArray -import com.android.server.pm.parsing.pkg.AndroidPackageInternal +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal import com.android.server.pm.pkg.component.ParsedActivityImpl diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index d54d608e48c2..a90b7d5ec7da 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -26,8 +26,7 @@ import android.os.PatternMatcher import android.os.Process import android.util.ArraySet import android.util.SparseArray -import com.android.server.pm.parsing.pkg.AndroidPackageInternal -import com.android.server.pm.pkg.AndroidPackage +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal import com.android.server.pm.pkg.component.ParsedActivityImpl diff --git a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java index 5c50acb13f30..b7cbac5a7f18 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java @@ -72,14 +72,27 @@ public class RefreshRateSettingsUtilsTest { @Test public void testFindHighestRefreshRateForDefaultDisplay() { - when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null); - assertEquals(DEFAULT_REFRESH_RATE, + when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock); + assertEquals(120, RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), /* delta= */ 0); + } - when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock); + @Test + public void testFindHighestRefreshRate() { + int displayId = 13; + when(mDisplayManagerMock.getDisplay(displayId)).thenReturn(mDisplayMock); assertEquals(120, + RefreshRateSettingsUtils.findHighestRefreshRate(mContext, displayId), + /* delta= */ 0); + } + + @Test + public void testFindHighestRefreshRate_DisplayIsNull() { + when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null); + assertEquals(DEFAULT_REFRESH_RATE, RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), /* delta= */ 0); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BaseModeRefreshRateVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BaseModeRefreshRateVoteTest.kt new file mode 100644 index 000000000000..3f72364005ab --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BaseModeRefreshRateVoteTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.mode + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.server.display.mode.DisplayModeDirector.VoteSummary +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + + +private const val BASE_REFRESH_RATE = 60f +private const val OTHER_BASE_REFRESH_RATE = 90f + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BaseModeRefreshRateVoteTest { + + private lateinit var baseModeVote: BaseModeRefreshRateVote + + @Before + fun setUp() { + baseModeVote = BaseModeRefreshRateVote(BASE_REFRESH_RATE) + } + + @Test + fun `updates summary with base mode refresh rate if not set`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + + baseModeVote.updateSummary(summary) + + assertThat(summary.appRequestBaseModeRefreshRate).isEqualTo(BASE_REFRESH_RATE) + } + + @Test + fun `keeps summary base mode refresh rate if set`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.appRequestBaseModeRefreshRate = OTHER_BASE_REFRESH_RATE + + baseModeVote.updateSummary(summary) + + assertThat(summary.appRequestBaseModeRefreshRate).isEqualTo(OTHER_BASE_REFRESH_RATE) + } + + @Test + fun `keeps summary with base mode refresh rate if vote refresh rate is negative`() { + val invalidBaseModeVote = BaseModeRefreshRateVote(-10f) + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + + invalidBaseModeVote.updateSummary(summary) + + assertThat(summary.appRequestBaseModeRefreshRate).isZero() + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/CombinedVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/CombinedVoteTest.kt new file mode 100644 index 000000000000..7f8da88ca996 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/CombinedVoteTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.mode + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.server.display.mode.DisplayModeDirector.VoteSummary +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.junit.MockitoJUnit + + +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CombinedVoteTest { + private lateinit var combinedVote: CombinedVote + + @get:Rule + val mockitoRule = MockitoJUnit.rule() + + private val mockVote1 = mock<Vote>() + private val mockVote2 = mock<Vote>() + + @Before + fun setUp() { + combinedVote = CombinedVote(listOf(mockVote1, mockVote2)) + } + + @Test + fun `delegates update to children`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + + combinedVote.updateSummary(summary) + + verify(mockVote1).updateSummary(summary) + verify(mockVote2).updateSummary(summary) + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisableRefreshRateSwitchingVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/DisableRefreshRateSwitchingVoteTest.kt new file mode 100644 index 000000000000..c624325d773c --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisableRefreshRateSwitchingVoteTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.mode + +import androidx.test.filters.SmallTest +import com.android.server.display.mode.DisplayModeDirector.VoteSummary +import com.google.common.truth.Truth.assertThat +import com.google.testing.junit.testparameterinjector.TestParameter +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(TestParameterInjector::class) +class DisableRefreshRateSwitchingVoteTest { + + @Test + fun `disabled refresh rate switching is not changed`( + @TestParameter voteDisableSwitching: Boolean + ) { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.disableRefreshRateSwitching = true + val vote = DisableRefreshRateSwitchingVote(voteDisableSwitching) + + vote.updateSummary(summary) + + assertThat(summary.disableRefreshRateSwitching).isTrue() + } + + @Test + fun `disables refresh rate switching if requested`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + val vote = DisableRefreshRateSwitchingVote(true) + + vote.updateSummary(summary) + + assertThat(summary.disableRefreshRateSwitching).isTrue() + } + + @Test + fun `does not disable refresh rate switching if not requested`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + val vote = DisableRefreshRateSwitchingVote(false) + + vote.updateSummary(summary) + + assertThat(summary.disableRefreshRateSwitching).isFalse() + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 499e7008febd..d0859232778d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -28,7 +28,6 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_R import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.server.display.mode.Vote.INVALID_SIZE; import static com.google.common.truth.Truth.assertThat; @@ -291,6 +290,7 @@ public class DisplayModeDirectorTest { }; private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY; + private static final int DISPLAY_ID_2 = Display.DEFAULT_DISPLAY + 1; private static final int MODE_ID = 1; private static final float TRANSITION_POINT = 0.763f; @@ -1192,7 +1192,9 @@ public class DisplayModeDirectorTest { assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/); vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); assertThat(vote).isNotNull(); - assertThat(vote.disableRefreshRateSwitching).isTrue(); + assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class); + DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote; + assertThat(disableVote.mDisableRefreshRateSwitching).isTrue(); } @Test @@ -1271,7 +1273,9 @@ public class DisplayModeDirectorTest { assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/); vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); assertThat(vote).isNotNull(); - assertThat(vote.disableRefreshRateSwitching).isTrue(); + assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class); + DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote; + assertThat(disableVote.mDisableRefreshRateSwitching).isTrue(); // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this // parameter to the necessary threshold @@ -1340,7 +1344,9 @@ public class DisplayModeDirectorTest { assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/); vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); assertThat(vote).isNotNull(); - assertThat(vote.disableRefreshRateSwitching).isTrue(); + assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class); + DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote; + assertThat(disableVote.mDisableRefreshRateSwitching).isTrue(); } @Test @@ -1423,7 +1429,9 @@ public class DisplayModeDirectorTest { assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/); vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); assertThat(vote).isNotNull(); - assertThat(vote.disableRefreshRateSwitching).isTrue(); + assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class); + DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote; + assertThat(disableVote.mDisableRefreshRateSwitching).isTrue(); // Set critical and check new refresh rate Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL); @@ -1435,7 +1443,9 @@ public class DisplayModeDirectorTest { assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/); vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); assertThat(vote).isNotNull(); - assertThat(vote.disableRefreshRateSwitching).isTrue(); + assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class); + disableVote = (DisableRefreshRateSwitchingVote) vote; + assertThat(disableVote.mDisableRefreshRateSwitching).isTrue(); } @Test @@ -1518,7 +1528,9 @@ public class DisplayModeDirectorTest { assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/); vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); assertThat(vote).isNotNull(); - assertThat(vote.disableRefreshRateSwitching).isTrue(); + assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class); + DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote; + assertThat(disableVote.mDisableRefreshRateSwitching).isTrue(); // Set critical and check new refresh rate Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL); @@ -1530,16 +1542,21 @@ public class DisplayModeDirectorTest { assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/); vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); assertThat(vote).isNotNull(); - assertThat(vote.disableRefreshRateSwitching).isTrue(); + assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class); + disableVote = (DisableRefreshRateSwitchingVote) vote; + assertThat(disableVote.mDisableRefreshRateSwitching).isTrue(); } @Test public void testPeakRefreshRate_FlagEnabled() { when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) .thenReturn(true); - float highestRefreshRate = 130; - doReturn(highestRefreshRate).when(() -> - RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext)); + float highestRefreshRate1 = 130; + float highestRefreshRate2 = 132; + doReturn(highestRefreshRate1).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); + doReturn(highestRefreshRate2).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID_2)); DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); @@ -1550,10 +1567,14 @@ public class DisplayModeDirectorTest { setPeakRefreshRate(Float.POSITIVE_INFINITY); - Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote vote1 = director.getVote(DISPLAY_ID, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + Vote vote2 = director.getVote(DISPLAY_ID_2, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - highestRefreshRate); + assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, + /* frameRateHigh= */ highestRefreshRate1); + assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, + /* frameRateHigh= */ highestRefreshRate2); } @Test @@ -1571,19 +1592,54 @@ public class DisplayModeDirectorTest { setPeakRefreshRate(peakRefreshRate); - Vote vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - peakRefreshRate); + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, + /* frameRateHigh= */ peakRefreshRate); } @Test - public void testMinRefreshRate_FlagEnabled() { + public void testPeakRefreshRate_DisplayChanged() { when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) .thenReturn(true); float highestRefreshRate = 130; doReturn(highestRefreshRate).when(() -> - RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext)); + RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + setPeakRefreshRate(Float.POSITIVE_INFINITY); + + Vote vote = director.getVote(DISPLAY_ID, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, + /* frameRateHigh= */ highestRefreshRate); + + // The highest refresh rate of the display changes + highestRefreshRate = 140; + doReturn(highestRefreshRate).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); + director.getDisplayObserver().onDisplayChanged(DISPLAY_ID); + + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, + /* frameRateHigh= */ highestRefreshRate); + } + + @Test + public void testMinRefreshRate_FlagEnabled() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(true); + float highestRefreshRate1 = 130; + float highestRefreshRate2 = 132; + doReturn(highestRefreshRate1).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); + doReturn(highestRefreshRate2).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID_2)); DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); @@ -1594,9 +1650,12 @@ public class DisplayModeDirectorTest { setMinRefreshRate(Float.POSITIVE_INFINITY); - Vote vote = director.getVote(Display.DEFAULT_DISPLAY, + Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + Vote vote2 = director.getVote(DISPLAY_ID_2, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate, + assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ highestRefreshRate1, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ highestRefreshRate2, /* frameRateHigh= */ Float.POSITIVE_INFINITY); } @@ -1615,13 +1674,44 @@ public class DisplayModeDirectorTest { setMinRefreshRate(minRefreshRate); - Vote vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate, /* frameRateHigh= */ Float.POSITIVE_INFINITY); } @Test + public void testMinRefreshRate_DisplayChanged() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(true); + float highestRefreshRate = 130; + doReturn(highestRefreshRate).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + setMinRefreshRate(Float.POSITIVE_INFINITY); + + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + + // The highest refresh rate of the display changes + highestRefreshRate = 140; + doReturn(highestRefreshRate).when(() -> + RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID)); + director.getDisplayObserver().onDisplayChanged(DISPLAY_ID); + + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + } + + @Test public void testSensorRegistration() { // First, configure brightness zones or DMD won't register for sensor data. final FakeDeviceConfig config = mInjector.getDeviceConfig(); @@ -1800,61 +1890,43 @@ public class DisplayModeDirectorTest { DisplayModeDirector director = createDirectorFromFpsRange(60, 90); director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0, 0); - Vote appRequestRefreshRate = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); - assertNotNull(appRequestRefreshRate); - assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity(); - assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero(); - assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity(); - assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse(); - assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate) - .isWithin(FLOAT_TOLERANCE).of(60); - assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE); - assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE); - - Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); - assertNotNull(appRequestSize); - assertThat(appRequestSize.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity(); - assertThat(appRequestSize.refreshRateRanges.render.min).isZero(); - assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity(); - assertThat(appRequestSize.disableRefreshRateSwitching).isFalse(); - assertThat(appRequestSize.appRequestBaseModeRefreshRate).isZero(); - assertThat(appRequestSize.height).isEqualTo(1000); - assertThat(appRequestSize.width).isEqualTo(1000); - - Vote appRequestRefreshRateRange = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); - assertNull(appRequestRefreshRateRange); + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class); + BaseModeRefreshRateVote baseModeVote = (BaseModeRefreshRateVote) vote; + assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60); + + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(SizeVote.class); + SizeVote sizeVote = (SizeVote) vote; + assertThat(sizeVote.mHeight).isEqualTo(1000); + assertThat(sizeVote.mWidth).isEqualTo(1000); + assertThat(sizeVote.mMinHeight).isEqualTo(1000); + assertThat(sizeVote.mMinWidth).isEqualTo(1000); + + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); + assertNull(vote); director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 90, 0, 0); - appRequestRefreshRate = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); - assertNotNull(appRequestRefreshRate); - assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity(); - assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero(); - assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity(); - assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse(); - assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate) - .isWithin(FLOAT_TOLERANCE).of(90); - assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE); - assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE); - - appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); - assertNotNull(appRequestSize); - assertThat(appRequestSize.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity(); - assertThat(appRequestSize.refreshRateRanges.render.min).isZero(); - assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity(); - assertThat(appRequestSize.height).isEqualTo(1000); - assertThat(appRequestSize.width).isEqualTo(1000); - - appRequestRefreshRateRange = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); - assertNull(appRequestRefreshRateRange); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class); + baseModeVote = (BaseModeRefreshRateVote) vote; + assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(90); + + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(SizeVote.class); + sizeVote = (SizeVote) vote; + assertThat(sizeVote.mHeight).isEqualTo(1000); + assertThat(sizeVote.mWidth).isEqualTo(1000); + assertThat(sizeVote.mMinHeight).isEqualTo(1000); + assertThat(sizeVote.mMinWidth).isEqualTo(1000); + + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); + assertNull(vote); } @Test @@ -1868,17 +1940,12 @@ public class DisplayModeDirectorTest { Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); assertNull(appRequestSize); - Vote appRequestRefreshRateRange = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); - assertNotNull(appRequestRefreshRateRange); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isPositiveInfinity(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min) - .isWithin(FLOAT_TOLERANCE).of(60); - assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90); - assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE); - assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE); + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class); + RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote; + assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(renderVote.mMaxRefreshRate).isAtLeast(90); director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 0); appRequestRefreshRate = @@ -1888,18 +1955,12 @@ public class DisplayModeDirectorTest { appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); assertNull(appRequestSize); - appRequestRefreshRateRange = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); - assertNotNull(appRequestRefreshRateRange); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isPositiveInfinity(); - - assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min) - .isWithin(FLOAT_TOLERANCE).of(90); - assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90); - assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE); - assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class); + renderVote = (RefreshRateVote.RenderVote) vote; + assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(renderVote.mMaxRefreshRate).isAtLeast(90); } @Test @@ -1913,18 +1974,12 @@ public class DisplayModeDirectorTest { Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); assertNull(appRequestSize); - Vote appRequestRefreshRateRange = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); - assertNotNull(appRequestRefreshRateRange); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isPositiveInfinity(); - - assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max) - .isWithin(FLOAT_TOLERANCE).of(90); - assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE); - assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE); + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class); + RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote; + assertThat(renderVote.mMinRefreshRate).isZero(); + assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90); director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 60); appRequestRefreshRate = @@ -1934,18 +1989,12 @@ public class DisplayModeDirectorTest { appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); assertNull(appRequestSize); - appRequestRefreshRateRange = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); - assertNotNull(appRequestRefreshRateRange); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isPositiveInfinity(); - - assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max) - .isWithin(FLOAT_TOLERANCE).of(60); - assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE); - assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class); + renderVote = (RefreshRateVote.RenderVote) vote; + assertThat(renderVote.mMinRefreshRate).isZero(); + assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(60); } @Test @@ -1969,41 +2018,27 @@ public class DisplayModeDirectorTest { DisplayModeDirector director = createDirectorFromFpsRange(60, 90); director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90, 90); - Vote appRequestRefreshRate = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); - assertNotNull(appRequestRefreshRate); - assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity(); - assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero(); - assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity(); - assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse(); - assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate) - .isWithin(FLOAT_TOLERANCE).of(60); - assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE); - assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE); - - Vote appRequestSize = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); - assertNotNull(appRequestSize); - assertThat(appRequestSize.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity(); - assertThat(appRequestSize.refreshRateRanges.render.min).isZero(); - assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity(); - assertThat(appRequestSize.height).isEqualTo(1000); - assertThat(appRequestSize.width).isEqualTo(1000); + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(BaseModeRefreshRateVote.class); + BaseModeRefreshRateVote baseModeVote = (BaseModeRefreshRateVote) vote; + assertThat(baseModeVote.mAppRequestBaseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60); - Vote appRequestRefreshRateRange = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); - assertNotNull(appRequestRefreshRateRange); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max) - .isPositiveInfinity(); - assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min) - .isWithin(FLOAT_TOLERANCE).of(90); - assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max) - .isWithin(FLOAT_TOLERANCE).of(90); - assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE); - assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(SizeVote.class); + SizeVote sizeVote = (SizeVote) vote; + assertThat(sizeVote.mHeight).isEqualTo(1000); + assertThat(sizeVote.mWidth).isEqualTo(1000); + assertThat(sizeVote.mMinHeight).isEqualTo(1000); + assertThat(sizeVote.mMinWidth).isEqualTo(1000); + + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE); + assertNotNull(vote); + assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class); + RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote; + assertThat(renderVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(renderVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90); } @Test @@ -3073,8 +3108,7 @@ public class DisplayModeDirectorTest { captor.getValue().onAuthenticationPossible(DISPLAY_ID, true); Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE); - assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90); - assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90); + assertVoteForPhysicalRefreshRate(vote, 90); } @Test @@ -3107,8 +3141,7 @@ public class DisplayModeDirectorTest { captor.getValue().onRequestEnabled(DISPLAY_ID); Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS); - assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90); - assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90); + assertVoteForPhysicalRefreshRate(vote, 90); } @Test @@ -3180,16 +3213,21 @@ public class DisplayModeDirectorTest { private void assertVoteForPhysicalRefreshRate(Vote vote, float refreshRate) { assertThat(vote).isNotNull(); - final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate); - assertThat(vote.refreshRateRanges.physical).isEqualTo(expectedRange); + assertThat(vote).isInstanceOf(CombinedVote.class); + CombinedVote combinedVote = (CombinedVote) vote; + RefreshRateVote.PhysicalVote physicalVote = + (RefreshRateVote.PhysicalVote) combinedVote.mVotes.get(0); + assertThat(physicalVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(refreshRate); + assertThat(physicalVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(refreshRate); } private void assertVoteForRenderFrameRateRange( Vote vote, float frameRateLow, float frameRateHigh) { assertThat(vote).isNotNull(); - final RefreshRateRange expectedRange = - new RefreshRateRange(frameRateLow, frameRateHigh); - assertThat(vote.refreshRateRanges.render).isEqualTo(expectedRange); + assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class); + RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote; + assertThat(renderVote.mMinRefreshRate).isEqualTo(frameRateLow); + assertThat(renderVote.mMaxRefreshRate).isEqualTo(frameRateHigh); } public static class FakeDeviceConfig extends FakeDeviceConfigInterface { @@ -3368,7 +3406,7 @@ public class DisplayModeDirectorTest { public static class FakesInjector implements DisplayModeDirector.Injector { private final FakeDeviceConfig mDeviceConfig; private final DisplayInfo mDisplayInfo; - private final Display mDisplay; + private final Map<Integer, Display> mDisplays; private boolean mDisplayInfoValid = true; private final DisplayManagerInternal mDisplayManagerInternal; private final StatusBarManagerInternal mStatusBarManagerInternal; @@ -3389,7 +3427,8 @@ public class DisplayModeDirectorTest { mDisplayInfo.defaultModeId = MODE_ID; mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID, 800, 600, /* refreshRate= */ 60)}; - mDisplay = createDisplay(DISPLAY_ID); + mDisplays = Map.of(DISPLAY_ID, createDisplay(DISPLAY_ID), + DISPLAY_ID_2, createDisplay(DISPLAY_ID_2)); mDisplayManagerInternal = displayManagerInternal; mStatusBarManagerInternal = statusBarManagerInternal; mSensorManagerInternal = sensorManagerInternal; @@ -3420,12 +3459,12 @@ public class DisplayModeDirectorTest { @Override public Display getDisplay(int displayId) { - return mDisplay; + return mDisplays.get(displayId); } @Override public Display[] getDisplays() { - return new Display[] { mDisplay }; + return mDisplays.values().toArray(new Display[0]); } @Override diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/PhysicalVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/PhysicalVoteTest.kt new file mode 100644 index 000000000000..547008e2fe56 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/PhysicalVoteTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.mode + +import androidx.test.filters.SmallTest +import com.android.server.display.mode.DisplayModeDirector.VoteSummary +import com.google.common.truth.Truth.assertThat +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +private const val MIN_REFRESH_RATE = 60f +private const val MAX_REFRESH_RATE = 90f + +@SmallTest +@RunWith(TestParameterInjector::class) +class PhysicalVoteTest { + private lateinit var physicalVote: RefreshRateVote.PhysicalVote + + @Before + fun setUp() { + physicalVote = RefreshRateVote.PhysicalVote(MIN_REFRESH_RATE, MAX_REFRESH_RATE) + } + + @Test + fun `updates minPhysicalRefreshRate if summary has less`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.minPhysicalRefreshRate = 45f + + physicalVote.updateSummary(summary) + + assertThat(summary.minPhysicalRefreshRate).isEqualTo(MIN_REFRESH_RATE) + } + + @Test + fun `does not update minPhysicalRefreshRate if summary has more`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.minPhysicalRefreshRate = 75f + + physicalVote.updateSummary(summary) + + assertThat(summary.minPhysicalRefreshRate).isEqualTo(75f) + } + + @Test + fun `updates maxPhysicalRefreshRate if summary has more`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.maxPhysicalRefreshRate = 120f + + physicalVote.updateSummary(summary) + + assertThat(summary.maxPhysicalRefreshRate).isEqualTo(MAX_REFRESH_RATE) + } + + @Test + fun `does not update maxPhysicalRefreshRate if summary has less`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.maxPhysicalRefreshRate = 75f + + physicalVote.updateSummary(summary) + + assertThat(summary.maxPhysicalRefreshRate).isEqualTo(75f) + } + + @Test + fun `updates maxRenderFrameRate if summary has more`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.maxRenderFrameRate = 120f + + physicalVote.updateSummary(summary) + + assertThat(summary.maxRenderFrameRate).isEqualTo(MAX_REFRESH_RATE) + } + + @Test + fun `does not update maxRenderFrameRate if summary has less`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.maxRenderFrameRate = 75f + + physicalVote.updateSummary(summary) + + assertThat(summary.maxRenderFrameRate).isEqualTo(75f) + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/RenderVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/RenderVoteTest.kt new file mode 100644 index 000000000000..868a89393d5f --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/RenderVoteTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.mode + +import androidx.test.filters.SmallTest +import com.android.server.display.mode.DisplayModeDirector.VoteSummary +import com.google.common.truth.Truth.assertThat +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +private const val MIN_REFRESH_RATE = 60f +private const val MAX_REFRESH_RATE = 90f + +@SmallTest +@RunWith(TestParameterInjector::class) +class RenderVoteTest { + + private lateinit var renderVote: RefreshRateVote.RenderVote + + @Before + fun setUp() { + renderVote = RefreshRateVote.RenderVote(MIN_REFRESH_RATE, MAX_REFRESH_RATE) + } + + @Test + fun `updates minRenderFrameRate if summary has less`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.minRenderFrameRate = 45f + + renderVote.updateSummary(summary) + + assertThat(summary.minRenderFrameRate).isEqualTo(MIN_REFRESH_RATE) + } + + @Test + fun `does not update minRenderFrameRate if summary has more`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.minRenderFrameRate = 75f + + renderVote.updateSummary(summary) + + assertThat(summary.minRenderFrameRate).isEqualTo(75f) + } + + @Test + fun `updates maxRenderFrameRate if summary has more`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.maxRenderFrameRate = 120f + + renderVote.updateSummary(summary) + + assertThat(summary.maxRenderFrameRate).isEqualTo(MAX_REFRESH_RATE) + } + + @Test + fun `does not update maxRenderFrameRate if summary has less`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.maxRenderFrameRate = 75f + + renderVote.updateSummary(summary) + + assertThat(summary.maxRenderFrameRate).isEqualTo(75f) + } + + @Test + fun `updates minPhysicalRefreshRate if summary has less`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.minPhysicalRefreshRate = 45f + + renderVote.updateSummary(summary) + + assertThat(summary.minPhysicalRefreshRate).isEqualTo(MIN_REFRESH_RATE) + } + + @Test + fun `does not update minPhysicalRefreshRate if summary has more`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.minPhysicalRefreshRate = 75f + + renderVote.updateSummary(summary) + + assertThat(summary.minPhysicalRefreshRate).isEqualTo(75f) + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SizeVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SizeVoteTest.kt new file mode 100644 index 000000000000..1c631b07a2ff --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SizeVoteTest.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.mode + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.server.display.mode.DisplayModeDirector.VoteSummary +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + + +private const val WIDTH = 800 +private const val HEIGHT = 1600 +private const val MIN_WIDTH = 400 +private const val MIN_HEIGHT = 1200 +@SmallTest +@RunWith(AndroidJUnit4::class) +class SizeVoteTest { + private lateinit var sizeVote: SizeVote + + @Before + fun setUp() { + sizeVote = SizeVote(WIDTH, HEIGHT, MIN_WIDTH, MIN_HEIGHT) + } + + @Test + fun `updates size if width and height not set and display resolution voting disabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ false) + summary.width = Vote.INVALID_SIZE + summary.height = Vote.INVALID_SIZE + summary.minWidth = 100 + summary.minHeight = 200 + + sizeVote.updateSummary(summary) + + assertThat(summary.width).isEqualTo(WIDTH) + assertThat(summary.height).isEqualTo(HEIGHT) + assertThat(summary.minWidth).isEqualTo(MIN_WIDTH) + assertThat(summary.minHeight).isEqualTo(MIN_HEIGHT) + } + + @Test + fun `does not update size if width set and display resolution voting disabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ false) + summary.width = 150 + summary.height = Vote.INVALID_SIZE + summary.minWidth = 100 + summary.minHeight = 200 + + sizeVote.updateSummary(summary) + + assertThat(summary.width).isEqualTo(150) + assertThat(summary.height).isEqualTo(Vote.INVALID_SIZE) + assertThat(summary.minWidth).isEqualTo(100) + assertThat(summary.minHeight).isEqualTo(200) + } + + @Test + fun `does not update size if height set and display resolution voting disabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ false) + summary.width = Vote.INVALID_SIZE + summary.height = 250 + summary.minWidth = 100 + summary.minHeight = 200 + + sizeVote.updateSummary(summary) + + assertThat(summary.width).isEqualTo(Vote.INVALID_SIZE) + assertThat(summary.height).isEqualTo(250) + assertThat(summary.minWidth).isEqualTo(100) + assertThat(summary.minHeight).isEqualTo(200) + } + + @Test + fun `updates width if summary has more and display resolution voting enabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.width = 850 + + sizeVote.updateSummary(summary) + + assertThat(summary.width).isEqualTo(WIDTH) + } + + @Test + fun `does not update width if summary has less and display resolution voting enabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.width = 750 + + sizeVote.updateSummary(summary) + + assertThat(summary.width).isEqualTo(750) + } + + @Test + fun `updates height if summary has more and display resolution voting enabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.height = 1650 + + sizeVote.updateSummary(summary) + + assertThat(summary.height).isEqualTo(HEIGHT) + } + + @Test + fun `does not update height if summary has less and display resolution voting enabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.height = 1550 + + sizeVote.updateSummary(summary) + + assertThat(summary.height).isEqualTo(1550) + } + + @Test + fun `updates minWidth if summary has less and display resolution voting enabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.width = 150 + summary.minWidth = 350 + + sizeVote.updateSummary(summary) + + assertThat(summary.minWidth).isEqualTo(MIN_WIDTH) + } + + @Test + fun `does not update minWidth if summary has more and display resolution voting enabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.width = 150 + summary.minWidth = 450 + + sizeVote.updateSummary(summary) + + assertThat(summary.minWidth).isEqualTo(450) + } + + @Test + fun `updates minHeight if summary has less and display resolution voting enabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.width = 150 + summary.minHeight = 1150 + + sizeVote.updateSummary(summary) + + assertThat(summary.minHeight).isEqualTo(MIN_HEIGHT) + } + + @Test + fun `does not update minHeight if summary has more and display resolution voting enabled`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.width = 150 + summary.minHeight = 1250 + + sizeVote.updateSummary(summary) + + assertThat(summary.minHeight).isEqualTo(1250) + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java index 9ab6ee5bd230..f6774017c523 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java @@ -17,6 +17,8 @@ package com.android.server.display.mode; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -102,17 +104,21 @@ public class SkinThermalStatusObserverTest { SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); assertEquals(1, displayVotes.size()); - Vote vote = displayVotes.get( - Vote.PRIORITY_SKIN_TEMPERATURE); - assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE); - assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE); + Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE); + + assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class); + RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote; + assertEquals(0, renderVote.mMinRefreshRate, FLOAT_TOLERANCE); + assertEquals(60, renderVote.mMaxRefreshRate, FLOAT_TOLERANCE); SparseArray<Vote> otherDisplayVotes = mStorage.getVotes(DISPLAY_ID_OTHER); assertEquals(1, otherDisplayVotes.size()); vote = otherDisplayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE); - assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE); - assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE); + assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class); + renderVote = (RefreshRateVote.RenderVote) vote; + assertEquals(0, renderVote.mMinRefreshRate, FLOAT_TOLERANCE); + assertEquals(60, renderVote.mMaxRefreshRate, FLOAT_TOLERANCE); } @Test @@ -167,8 +173,10 @@ public class SkinThermalStatusObserverTest { SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); assertEquals(1, displayVotes.size()); Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE); - assertEquals(90, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE); - assertEquals(120, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE); + assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class); + RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote; + assertEquals(90, renderVote.mMinRefreshRate, FLOAT_TOLERANCE); + assertEquals(120, renderVote.mMaxRefreshRate, FLOAT_TOLERANCE); assertEquals(0, mStorage.getVotes(DISPLAY_ID_OTHER).size()); } @@ -188,8 +196,10 @@ public class SkinThermalStatusObserverTest { SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID_ADDED); Vote vote = displayVotes.get(Vote.PRIORITY_SKIN_TEMPERATURE); - assertEquals(0, vote.refreshRateRanges.render.min, FLOAT_TOLERANCE); - assertEquals(60, vote.refreshRateRanges.render.max, FLOAT_TOLERANCE); + assertThat(vote).isInstanceOf(RefreshRateVote.RenderVote.class); + RefreshRateVote.RenderVote renderVote = (RefreshRateVote.RenderVote) vote; + assertEquals(0, renderVote.mMinRefreshRate, FLOAT_TOLERANCE); + assertEquals(60, renderVote.mMaxRefreshRate, FLOAT_TOLERANCE); } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt new file mode 100644 index 000000000000..cc8800395d1e --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.mode + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.server.display.mode.DisplayModeDirector.VoteSummary +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SupportedModesVoteTest { + private val supportedModes = listOf( + SupportedModesVote.SupportedMode(60f, 90f ), + SupportedModesVote.SupportedMode(120f, 240f ) + ) + + private val otherMode = SupportedModesVote.SupportedMode(120f, 120f ) + + private lateinit var supportedModesVote: SupportedModesVote + + @Before + fun setUp() { + supportedModesVote = SupportedModesVote(supportedModes) + } + + @Test + fun `adds supported modes if supportedModes in summary is null`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + + supportedModesVote.updateSummary(summary) + + assertThat(summary.supportedModes).containsExactlyElementsIn(supportedModes) + } + + @Test + fun `does not add supported modes if summary has empty list of modes`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.supportedModes = ArrayList() + + supportedModesVote.updateSummary(summary) + + assertThat(summary.supportedModes).isEmpty() + } + + @Test + fun `filters out modes that does not match vote`() { + val summary = VoteSummary(/* isDisplayResolutionRangeVotingEnabled= */ true) + summary.supportedModes = ArrayList(listOf(otherMode, supportedModes[0])) + + supportedModesVote.updateSummary(summary) + + assertThat(summary.supportedModes).containsExactly(supportedModes[0]) + } +}
\ No newline at end of file diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java index 92d1118d0f1e..4f672f81a9cb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java @@ -19,6 +19,7 @@ package com.android.server.appop; import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM; import static android.app.AppOpsManager.OP_USE_FULL_SCREEN_INTENT; import static android.app.AppOpsManager._NUM_OP; +import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -208,8 +209,8 @@ public class AppOpsUpgradeTest { private void assertSameModes(AppOpsCheckingServiceImpl testService, int op1, int op2) { for (int uid : testService.getUidsWithNonDefaultModes()) { assertEquals( - testService.getUidMode(uid, op1), - testService.getUidMode(uid, op2) + testService.getUidMode(uid, PERSISTENT_DEVICE_ID_DEFAULT, op1), + testService.getUidMode(uid, PERSISTENT_DEVICE_ID_DEFAULT, op2) ); } for (UserPackage pkg : testService.getPackagesWithNonDefaultModes()) { @@ -275,7 +276,9 @@ public class AppOpsUpgradeTest { } else { expectedMode = previousMode; } - int mode = testService.getUidMode(uid, OP_SCHEDULE_EXACT_ALARM); + int mode = + testService.getUidMode( + uid, PERSISTENT_DEVICE_ID_DEFAULT, OP_SCHEDULE_EXACT_ALARM); assertEquals(expectedMode, mode); } } @@ -284,7 +287,9 @@ public class AppOpsUpgradeTest { int[] unrelatedUidsInFile = {10225, 10178}; for (int uid : unrelatedUidsInFile) { - int mode = testService.getUidMode(uid, OP_SCHEDULE_EXACT_ALARM); + int mode = + testService.getUidMode( + uid, PERSISTENT_DEVICE_ID_DEFAULT, OP_SCHEDULE_EXACT_ALARM); assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM), mode); } } @@ -331,7 +336,9 @@ public class AppOpsUpgradeTest { final int uid = UserHandle.getUid(userId, appId); final int expectedMode = AppOpsManager.opToDefaultMode(OP_USE_FULL_SCREEN_INTENT); synchronized (testService) { - int mode = testService.getUidMode(uid, OP_USE_FULL_SCREEN_INTENT); + int mode = + testService.getUidMode( + uid, PERSISTENT_DEVICE_ID_DEFAULT, OP_USE_FULL_SCREEN_INTENT); assertEquals(expectedMode, mode); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index be33b1b6f34f..46806f334a27 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -55,6 +55,8 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.spy import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder import com.android.internal.R +import com.android.internal.pm.parsing.pkg.ParsedPackage +import com.android.internal.pm.pkg.parsing.ParsingPackage import com.android.server.LocalManagerRegistry import com.android.server.LocalServices import com.android.server.LockGuard @@ -66,10 +68,8 @@ import com.android.server.pm.dex.DexManager import com.android.server.pm.dex.DynamicCodeLogger import com.android.server.pm.parsing.PackageParser2 import com.android.server.pm.parsing.pkg.PackageImpl -import com.android.server.pm.parsing.pkg.ParsedPackage import com.android.server.pm.permission.PermissionManagerServiceInternal import com.android.server.pm.pkg.AndroidPackage -import com.android.server.pm.pkg.parsing.ParsingPackage import com.android.server.pm.pkg.parsing.ParsingPackageUtils import com.android.server.pm.resolution.ComponentResolver import com.android.server.pm.snapshot.PackageDataSnapshot diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt index b8f726b393cc..e685c3fa9fa4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt @@ -25,13 +25,13 @@ import android.os.storage.StorageManager import android.os.UserHandle import android.util.ArrayMap import android.util.PackageUtils +import com.android.internal.pm.parsing.pkg.ParsedPackage import com.android.server.SystemConfig.SharedLibraryEntry import com.android.server.compat.PlatformCompat import com.android.server.extendedtestutils.wheneverStatic import com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.PackageImpl -import com.android.server.pm.parsing.pkg.ParsedPackage import com.android.server.testutils.any import com.android.server.testutils.eq import com.android.server.testutils.mock diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 3dbab1360af4..15ae4634b573 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -453,7 +453,7 @@ public class WallpaperManagerServiceTests { doAnswer(invocation -> timestamps[0] = SystemClock.elapsedRealtime()) .when(sContext).sendBroadcastAsUser(any(), any()); doAnswer(invocation -> timestamps[1] = SystemClock.elapsedRealtime()) - .when(mService).notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM); + .when(mService).notifyWallpaperColorsChanged(wallpaper); assertNull(wallpaper.wallpaperObserver); mService.switchUser(wallpaper.userId, null); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java index 2003d04e1dbc..ca7de7c3f325 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java @@ -90,6 +90,10 @@ public class AggregatedPowerStatsTest { private AggregatedPowerStats prepareAggregatePowerStats() { AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig); + + PowerStats ps = new PowerStats(mPowerComponentDescriptor); + stats.addPowerStats(ps, 0); + stats.addClockUpdate(1000, 456); stats.setDuration(789); @@ -100,7 +104,6 @@ public class AggregatedPowerStatsTest { stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE, BatteryConsumer.PROCESS_STATE_FOREGROUND, 2000); - PowerStats ps = new PowerStats(mPowerComponentDescriptor); ps.stats[0] = 100; ps.stats[1] = 987; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java index 663af5da48d2..9c2834d31609 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java @@ -215,7 +215,7 @@ public class BatteryExternalStatsWorkerTest { public class TestBatteryStatsImpl extends BatteryStatsImpl { public TestBatteryStatsImpl(Context context) { - super(Clock.SYSTEM_CLOCK, null); + super(Clock.SYSTEM_CLOCK, null, null, null); mPowerProfile = new PowerProfile(context, true /* forTest */); SparseArray<int[]> cpusByPolicy = new SparseArray<>(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java index 55ffa1a15a6b..f9f32b2e7091 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java @@ -37,6 +37,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.os.BatteryStats; +import android.os.Handler; +import android.os.Looper; import android.os.UserHandle; import android.util.SparseArray; import android.util.SparseLongArray; @@ -97,6 +99,7 @@ public class BatteryStatsCpuTimesTest { BatteryStatsImpl.UserInfoProvider mUserInfoProvider; private MockClock mClocks; + private PowerStatsUidResolver mPowerStatsUidResolver; private MockBatteryStatsImpl mBatteryStatsImpl; private KernelCpuSpeedReader[] mKernelCpuSpeedReaders; @@ -105,7 +108,9 @@ public class BatteryStatsCpuTimesTest { MockitoAnnotations.initMocks(this); mClocks = new MockClock(); - mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks) + Handler handler = new Handler(Looper.getMainLooper()); + mPowerStatsUidResolver = new PowerStatsUidResolver(); + mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks, null, handler, mPowerStatsUidResolver) .setTestCpuScalingPolicies() .setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader) .setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader) @@ -374,7 +379,7 @@ public class BatteryStatsCpuTimesTest { // PRECONDITIONS final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42); - mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid); + mPowerStatsUidResolver.noteIsolatedUidAdded(isolatedUid, ownerUid); final long[][] deltasUs = { {9379, 3332409833484L}, {493247, 723234}, {3247819, 123348} }; @@ -965,7 +970,7 @@ public class BatteryStatsCpuTimesTest { // PRECONDITIONS final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42); - mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid); + mPowerStatsUidResolver.noteIsolatedUidAdded(isolatedUid, ownerUid); final long[][] deltasMs = { {3, 12, 55, 100, 32}, {32483274, 232349349, 123, 2398, 0}, diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java index 5ebc6ca3f558..8d51592667c8 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java @@ -39,14 +39,22 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.UidTraffic; +import android.content.Context; +import android.os.BatteryConsumer; +import android.os.BatteryManager; import android.os.BatteryStats; +import android.os.BatteryUsageStats; import android.os.BluetoothBatteryStats; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; import android.os.Parcel; import android.os.WakeLockStats; import android.os.WorkSource; import android.util.SparseArray; import android.view.Display; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -65,6 +73,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.File; +import java.time.Instant; import java.util.List; @LargeTest @@ -93,6 +103,11 @@ public class BatteryStatsImplTest { private final MockClock mMockClock = new MockClock(); private MockBatteryStatsImpl mBatteryStatsImpl; + private Handler mHandler; + private PowerStatsStore mPowerStatsStore; + private BatteryUsageStatsProvider mBatteryUsageStatsProvider; + @Mock + private PowerStatsExporter mPowerStatsExporter; @Before public void setUp() { @@ -103,12 +118,23 @@ public class BatteryStatsImplTest { when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true); when(mKernelWakelockReader.readKernelWakelockStats( any(KernelWakelockStats.class))).thenReturn(mKernelWakelockStats); - mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock) + HandlerThread bgThread = new HandlerThread("bg thread"); + bgThread.start(); + mHandler = new Handler(bgThread.getLooper()); + mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock, null, mHandler) .setPowerProfile(mPowerProfile) .setCpuScalingPolicies(mCpuScalingPolicies) .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader) .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader) .setKernelWakelockReader(mKernelWakelockReader); + + final Context context = InstrumentationRegistry.getContext(); + File systemDir = context.getCacheDir(); + mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, + new AggregatedPowerStatsConfig()); + mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerStatsExporter, + mPowerProfile, mBatteryStatsImpl.getCpuScalingPolicies(), mPowerStatsStore, + mMockClock); } @Test @@ -754,4 +780,76 @@ public class BatteryStatsImplTest { parcel.recycle(); return info; } + + @Test + public void storeBatteryUsageStatsOnReset() { + mBatteryStatsImpl.forceRecordAllHistory(); + + mMockClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli(); + mMockClock.realtime = 7654321; + + synchronized (mBatteryStatsImpl) { + mBatteryStatsImpl.setOnBatteryLocked(mMockClock.realtime, mMockClock.uptime, true, + BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0); + // Will not save to PowerStatsStore because "saveBatteryUsageStatsOnReset" has not + // been called yet. + mBatteryStatsImpl.resetAllStatsAndHistoryLocked( + BatteryStatsImpl.RESET_REASON_ADB_COMMAND); + } + + assertThat(mPowerStatsStore.getTableOfContents()).isEmpty(); + + mBatteryStatsImpl.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, + mPowerStatsStore); + + synchronized (mBatteryStatsImpl) { + mBatteryStatsImpl.noteFlashlightOnLocked(42, mMockClock.realtime, mMockClock.uptime); + } + + mMockClock.realtime += 60000; + mMockClock.currentTime += 60000; + + synchronized (mBatteryStatsImpl) { + mBatteryStatsImpl.noteFlashlightOffLocked(42, mMockClock.realtime, mMockClock.uptime); + } + + mMockClock.realtime += 60000; + mMockClock.currentTime += 60000; + + // Battery stats reset should have the side-effect of saving accumulated battery usage stats + synchronized (mBatteryStatsImpl) { + mBatteryStatsImpl.resetAllStatsAndHistoryLocked( + BatteryStatsImpl.RESET_REASON_ADB_COMMAND); + } + + // Await completion + ConditionVariable done = new ConditionVariable(); + mHandler.post(done::open); + done.block(); + + List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents(); + assertThat(contents).hasSize(1); + + PowerStatsSpan.Metadata metadata = contents.get(0); + + PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(), + BatteryUsageStatsSection.TYPE); + assertThat(span).isNotNull(); + + List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames(); + assertThat(timeFrames).hasSize(1); + assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321); + assertThat(timeFrames.get(0).duration).isEqualTo(120000); + + List<PowerStatsSpan.Section> sections = span.getSections(); + assertThat(sections).hasSize(1); + + PowerStatsSpan.Section section = sections.get(0); + assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE); + BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats(); + assertThat(bus.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isEqualTo(60000); + } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java index 7ef1a3fd0d83..24c67f83788b 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java @@ -35,6 +35,8 @@ import android.app.usage.NetworkStatsManager; import android.os.BatteryStats; import android.os.BatteryStats.HistoryItem; import android.os.BatteryStats.Uid.Sensor; +import android.os.Handler; +import android.os.Looper; import android.os.Process; import android.os.UserHandle; import android.os.WorkSource; @@ -155,7 +157,9 @@ public class BatteryStatsNoteTest extends TestCase { @SmallTest public void testNoteStartWakeLocked_isolatedUid() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, + new Handler(Looper.getMainLooper()), uidResolver); int pid = 10; String name = "name"; @@ -165,7 +169,7 @@ public class BatteryStatsNoteTest extends TestCase { isolatedWorkChain.addNode(ISOLATED_UID, name); // Map ISOLATED_UID to UID. - bi.addIsolatedUidLocked(ISOLATED_UID, UID); + uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID); bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); @@ -195,7 +199,9 @@ public class BatteryStatsNoteTest extends TestCase { @SmallTest public void testNoteStartWakeLocked_isolatedUidRace() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, + new Handler(Looper.getMainLooper()), uidResolver); int pid = 10; String name = "name"; @@ -205,7 +211,7 @@ public class BatteryStatsNoteTest extends TestCase { isolatedWorkChain.addNode(ISOLATED_UID, name); // Map ISOLATED_UID to UID. - bi.addIsolatedUidLocked(ISOLATED_UID, UID); + uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID); bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); @@ -216,7 +222,7 @@ public class BatteryStatsNoteTest extends TestCase { bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); clocks.realtime = clocks.uptime = 150; - bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime); + uidResolver.releaseIsolatedUid(ISOLATED_UID); clocks.realtime = clocks.uptime = 220; bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName, @@ -237,8 +243,9 @@ public class BatteryStatsNoteTest extends TestCase { @SmallTest public void testNoteLongPartialWakelockStart_isolatedUid() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); - + PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, + new Handler(Looper.getMainLooper()), uidResolver); bi.setRecordAllHistoryLocked(true); bi.forceRecordAllHistory(); @@ -251,7 +258,7 @@ public class BatteryStatsNoteTest extends TestCase { isolatedWorkChain.addNode(ISOLATED_UID, name); // Map ISOLATED_UID to UID. - bi.addIsolatedUidLocked(ISOLATED_UID, UID); + uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID); bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); @@ -290,8 +297,9 @@ public class BatteryStatsNoteTest extends TestCase { @SmallTest public void testNoteLongPartialWakelockStart_isolatedUidRace() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); - + PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, + new Handler(Looper.getMainLooper()), uidResolver); bi.setRecordAllHistoryLocked(true); bi.forceRecordAllHistory(); @@ -304,7 +312,7 @@ public class BatteryStatsNoteTest extends TestCase { isolatedWorkChain.addNode(ISOLATED_UID, name); // Map ISOLATED_UID to UID. - bi.addIsolatedUidLocked(ISOLATED_UID, UID); + uidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID); bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP); @@ -314,7 +322,7 @@ public class BatteryStatsNoteTest extends TestCase { bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); clocks.realtime = clocks.uptime = 150; - bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime); + uidResolver.releaseIsolatedUid(ISOLATED_UID); clocks.realtime = clocks.uptime = 220; bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index b1da1fc88378..7148b164efa9 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java @@ -28,9 +28,7 @@ import android.os.BatteryManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; +import android.os.ConditionVariable; import android.os.Parcel; import android.os.Process; import android.os.UidBatteryConsumer; @@ -40,7 +38,6 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BatteryStatsHistoryIterator; -import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; import org.junit.Rule; @@ -72,10 +69,11 @@ public class BatteryUsageStatsProviderTest { BatteryStatsImpl batteryStats = prepareBatteryStats(); Context context = InstrumentationRegistry.getContext(); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock); final BatteryUsageStats batteryUsageStats = - provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT); + provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT); final List<UidBatteryConsumer> uidBatteryConsumers = batteryUsageStats.getUidBatteryConsumers(); @@ -99,10 +97,11 @@ public class BatteryUsageStatsProviderTest { BatteryStatsImpl batteryStats = prepareBatteryStats(); Context context = InstrumentationRegistry.getContext(); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock); final BatteryUsageStats batteryUsageStats = - provider.getBatteryUsageStats( + provider.getBatteryUsageStats(batteryStats, new BatteryUsageStatsQuery.Builder() .includePowerComponents( new int[]{BatteryConsumer.POWER_COMPONENT_AUDIO}) @@ -204,10 +203,11 @@ public class BatteryUsageStatsProviderTest { } Context context = InstrumentationRegistry.getContext(); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock); final BatteryUsageStats batteryUsageStats = - provider.getBatteryUsageStats( + provider.getBatteryUsageStats(batteryStats, new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build()); Parcel in = Parcel.obtain(); @@ -292,11 +292,11 @@ public class BatteryUsageStatsProviderTest { } Context context = InstrumentationRegistry.getContext(); - BatteryUsageStatsProvider - provider = new BatteryUsageStatsProvider(context, batteryStats); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock); final BatteryUsageStats batteryUsageStats = - provider.getBatteryUsageStats( + provider.getBatteryUsageStats(batteryStats, new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build()); Parcel parcel = Parcel.obtain(); @@ -352,27 +352,22 @@ public class BatteryUsageStatsProviderTest { @Test public void shouldUpdateStats() { - Context context = InstrumentationRegistry.getContext(); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, - mStatsRule.getBatteryStats()); - final List<BatteryUsageStatsQuery> queries = List.of( new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(1000).build(), new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(2000).build() ); - mStatsRule.setTime(10500, 0); - assertThat(provider.shouldUpdateStats(queries, 10000)).isFalse(); + assertThat(BatteryUsageStatsProvider.shouldUpdateStats(queries, + 10500, 10000)).isFalse(); - mStatsRule.setTime(11500, 0); - assertThat(provider.shouldUpdateStats(queries, 10000)).isTrue(); + assertThat(BatteryUsageStatsProvider.shouldUpdateStats(queries, + 11500, 10000)).isTrue(); } @Test public void testAggregateBatteryStats() { Context context = InstrumentationRegistry.getContext(); BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); - MonotonicClock monotonicClock = new MonotonicClock(0, mStatsRule.getMockClock()); setTime(5 * MINUTE_IN_MS); synchronized (batteryStats) { @@ -381,14 +376,17 @@ public class BatteryUsageStatsProviderTest { PowerStatsStore powerStatsStore = new PowerStatsStore( new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"), - new TestHandler(), null); + mStatsRule.getHandler(), null); + powerStatsStore.reset(); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, - batteryStats, powerStatsStore); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore, + mMockClock); - batteryStats.setBatteryResetListener(reason -> - powerStatsStore.storeBatteryUsageStats(monotonicClock.monotonicTime(), - provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT))); + batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore); + synchronized (batteryStats) { + batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); + } synchronized (batteryStats) { batteryStats.noteFlashlightOnLocked(APP_UID, @@ -441,11 +439,16 @@ public class BatteryUsageStatsProviderTest { } setTime(95 * MINUTE_IN_MS); + // Await completion + ConditionVariable done = new ConditionVariable(); + mStatsRule.getHandler().post(done::open); + done.block(); + // Include the first and the second snapshot, but not the third or current BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() .aggregateSnapshots(20 * MINUTE_IN_MS, 60 * MINUTE_IN_MS) .build(); - final BatteryUsageStats stats = provider.getBatteryUsageStats(query); + final BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, query); assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * MINUTE_IN_MS); @@ -499,30 +502,19 @@ public class BatteryUsageStatsProviderTest { when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE)) .thenReturn(span1); - BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, - batteryStats, powerStatsStore); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null, + mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore, + mMockClock); BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() .aggregateSnapshots(0, 3000) .build(); - final BatteryUsageStats stats = provider.getBatteryUsageStats(query); + final BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, query); assertThat(stats.getCustomPowerComponentNames()) .isEqualTo(batteryStats.getCustomEnergyConsumerNames()); assertThat(stats.getStatsDuration()).isEqualTo(1234); } - private static class TestHandler extends Handler { - TestHandler() { - super(Looper.getMainLooper()); - } - - @Override - public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - msg.getCallback().run(); - return true; - } - } - private static final Random sRandom = new Random(); /** diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 0b1095483dd0..e61dd0b41423 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -28,6 +28,8 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.Handler; +import android.os.HandlerThread; import android.os.UidBatteryConsumer; import android.os.UserBatteryConsumer; import android.util.SparseArray; @@ -57,6 +59,7 @@ public class BatteryUsageStatsRule implements TestRule { private final PowerProfile mPowerProfile; private final MockClock mMockClock = new MockClock(); private final MockBatteryStatsImpl mBatteryStats; + private final Handler mHandler; private BatteryUsageStats mBatteryUsageStats; private boolean mScreenOn; @@ -73,10 +76,13 @@ public class BatteryUsageStatsRule implements TestRule { } public BatteryUsageStatsRule(long currentTime, File historyDir) { + HandlerThread bgThread = new HandlerThread("bg thread"); + bgThread.start(); + mHandler = new Handler(bgThread.getLooper()); mContext = InstrumentationRegistry.getContext(); mPowerProfile = spy(new PowerProfile(mContext, true /* forTest */)); mMockClock.currentTime = currentTime; - mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir); + mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler); mBatteryStats.setPowerProfile(mPowerProfile); mCpusByPolicy.put(0, new int[]{0, 1, 2, 3}); @@ -92,6 +98,10 @@ public class BatteryUsageStatsRule implements TestRule { return mMockClock; } + public Handler getHandler() { + return mHandler; + } + public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) { mPowerProfile.forceInitForTesting(mContext, xmlId); return this; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java index 79084cc1b04d..8ca4ff6f86f5 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java @@ -192,7 +192,7 @@ public class CpuAggregatedPowerStatsProcessorTest { private static class MockPowerComponentAggregatedPowerStats extends PowerComponentAggregatedPowerStats { - private final CpuPowerStatsCollector.StatsArrayLayout mStatsLayout; + private final CpuPowerStatsCollector.CpuStatsArrayLayout mStatsLayout; private final PowerStats.Descriptor mDescriptor; private HashMap<String, long[]> mDeviceStats = new HashMap<>(); private HashMap<String, long[]> mUidStats = new HashMap<>(); @@ -203,10 +203,10 @@ public class CpuAggregatedPowerStatsProcessorTest { MockPowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config, boolean useEnergyConsumers) { super(config); - mStatsLayout = new CpuPowerStatsCollector.StatsArrayLayout(); + mStatsLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout(); mStatsLayout.addDeviceSectionCpuTimeByScalingStep(3); mStatsLayout.addDeviceSectionCpuTimeByCluster(2); - mStatsLayout.addDeviceSectionUptime(); + mStatsLayout.addDeviceSectionUsageDuration(); if (useEnergyConsumers) { mStatsLayout.addDeviceSectionEnergyConsumers(2); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java index bc211df5f143..64d5414bf66c 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java @@ -19,6 +19,7 @@ package com.android.server.power.stats; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -56,6 +57,9 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @SmallTest public class CpuPowerStatsCollectorTest { + private static final int ISOLATED_UID = 99123; + private static final int UID_1 = 42; + private static final int UID_2 = 99; private Context mContext; private final MockClock mMockClock = new MockClock(); private final HandlerThread mHandlerThread = new HandlerThread("test"); @@ -63,6 +67,8 @@ public class CpuPowerStatsCollectorTest { private PowerStats mCollectedStats; private PowerProfile mPowerProfile; @Mock + private PowerStatsUidResolver mUidResolver; + @Mock private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader; @Mock private PowerStatsInternal mPowerStatsInternal; @@ -76,6 +82,14 @@ public class CpuPowerStatsCollectorTest { mHandlerThread.start(); mHandler = mHandlerThread.getThreadHandler(); when(mMockKernelCpuStatsReader.nativeIsSupportedFeature()).thenReturn(true); + when(mUidResolver.mapUid(anyInt())).thenAnswer(invocation -> { + int uid = invocation.getArgument(0); + if (uid == ISOLATED_UID) { + return UID_2; + } else { + return uid; + } + }); } @Test @@ -156,14 +170,14 @@ public class CpuPowerStatsCollectorTest { assertThat(descriptor.name).isEqualTo("cpu"); assertThat(descriptor.statsArrayLength).isEqualTo(13); assertThat(descriptor.uidStatsArrayLength).isEqualTo(5); - CpuPowerStatsCollector.StatsArrayLayout layout = - new CpuPowerStatsCollector.StatsArrayLayout(); + CpuPowerStatsCollector.CpuStatsArrayLayout layout = + new CpuPowerStatsCollector.CpuStatsArrayLayout(); layout.fromExtras(descriptor.extras); long[] deviceStats = new long[descriptor.statsArrayLength]; layout.setTimeByScalingStep(deviceStats, 2, 42); layout.setConsumedEnergy(deviceStats, 1, 43); - layout.setUptime(deviceStats, 44); + layout.setUsageDuration(deviceStats, 44); layout.setDevicePowerEstimate(deviceStats, 45); long[] uidStats = new long[descriptor.uidStatsArrayLength]; @@ -173,10 +187,10 @@ public class CpuPowerStatsCollectorTest { assertThat(layout.getCpuScalingStepCount()).isEqualTo(7); assertThat(layout.getTimeByScalingStep(deviceStats, 2)).isEqualTo(42); - assertThat(layout.getCpuClusterEnergyConsumerCount()).isEqualTo(2); + assertThat(layout.getEnergyConsumerCount()).isEqualTo(2); assertThat(layout.getConsumedEnergy(deviceStats, 1)).isEqualTo(43); - assertThat(layout.getUptime(deviceStats)).isEqualTo(44); + assertThat(layout.getUsageDuration(deviceStats)).isEqualTo(44); assertThat(layout.getDevicePowerEstimate(deviceStats)).isEqualTo(45); @@ -195,14 +209,15 @@ public class CpuPowerStatsCollectorTest { mockEnergyConsumers(); CpuPowerStatsCollector collector = createCollector(8, 0); - CpuPowerStatsCollector.StatsArrayLayout layout = - new CpuPowerStatsCollector.StatsArrayLayout(); + CpuPowerStatsCollector.CpuStatsArrayLayout layout = + new CpuPowerStatsCollector.CpuStatsArrayLayout(); layout.fromExtras(collector.getPowerStatsDescriptor().extras); mockKernelCpuStats(new long[]{1111, 2222, 3333}, new SparseArray<>() {{ - put(42, new long[]{100, 200}); - put(99, new long[]{300, 600}); + put(UID_1, new long[]{100, 200}); + put(UID_2, new long[]{100, 150}); + put(ISOLATED_UID, new long[]{200, 450}); }}, 0, 1234); mMockClock.uptime = 1000; @@ -219,19 +234,19 @@ public class CpuPowerStatsCollectorTest { assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 0)).isEqualTo(0); assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(0); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 0)) .isEqualTo(100); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 1)) .isEqualTo(200); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 0)) .isEqualTo(300); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 1)) .isEqualTo(600); mockKernelCpuStats(new long[]{5555, 4444, 3333}, new SparseArray<>() {{ - put(42, new long[]{123, 234}); - put(99, new long[]{345, 678}); + put(UID_1, new long[]{123, 234}); + put(ISOLATED_UID, new long[]{245, 528}); }}, 1234, 3421); mMockClock.uptime = 2000; @@ -249,13 +264,13 @@ public class CpuPowerStatsCollectorTest { // 700 * 1000 / 3500 assertThat(layout.getConsumedEnergy(mCollectedStats.stats, 1)).isEqualTo(200); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 0)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 0)) .isEqualTo(23); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(42), 1)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_1), 1)) .isEqualTo(34); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 0)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 0)) .isEqualTo(45); - assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(99), 1)) + assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 1)) .isEqualTo(78); } @@ -282,9 +297,9 @@ public class CpuPowerStatsCollectorTest { private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) { CpuPowerStatsCollector collector = new CpuPowerStatsCollector(mCpuScalingPolicies, - mPowerProfile, mHandler, mMockKernelCpuStatsReader, () -> mPowerStatsInternal, - () -> 3500, 60_000, mMockClock, defaultCpuPowerBrackets, - defaultCpuPowerBracketsPerEnergyConsumer); + mPowerProfile, mHandler, mMockKernelCpuStatsReader, mUidResolver, + () -> mPowerStatsInternal, () -> 3500, 60_000, mMockClock, + defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer); collector.addConsumer(stats -> mCollectedStats = stats); collector.setEnabled(true); return collector; @@ -375,8 +390,8 @@ public class CpuPowerStatsCollectorTest { } private static int[] getScalingStepToPowerBracketMap(CpuPowerStatsCollector collector) { - CpuPowerStatsCollector.StatsArrayLayout layout = - new CpuPowerStatsCollector.StatsArrayLayout(); + CpuPowerStatsCollector.CpuStatsArrayLayout layout = + new CpuPowerStatsCollector.CpuStatsArrayLayout(); layout.fromExtras(collector.getPowerStatsDescriptor().extras); return layout.getScalingStepToPowerBracketMap(); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 4150972ab69a..fb71ac83a8d9 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -61,16 +61,23 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { } MockBatteryStatsImpl(Clock clock, File historyDirectory) { - super(clock, historyDirectory); + this(clock, historyDirectory, new Handler(Looper.getMainLooper())); + } + + MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler) { + this(clock, historyDirectory, handler, new PowerStatsUidResolver()); + } + + MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler, + PowerStatsUidResolver powerStatsUidResolver) { + super(clock, historyDirectory, handler, powerStatsUidResolver); initTimersAndCounters(); setMaxHistoryBuffer(128 * 1024); setExternalStatsSyncLocked(mExternalStatsSync); informThatAllExternalStatsAreFlushed(); - // A no-op handler. - mHandler = new Handler(Looper.getMainLooper()) { - }; + mHandler = handler; mCpuUidFreqTimeReader = mock(KernelCpuUidFreqTimeReader.class); mKernelWakelockReader = null; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java index b52fc8a7c727..67049871f396 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java @@ -76,9 +76,18 @@ public class PowerStatsAggregatorTest { @Test public void stateUpdates() { + PowerStats.Descriptor descriptor = + new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, + new PersistableBundle()); + PowerStats powerStats = new PowerStats(descriptor); + mClock.currentTime = 1222156800000L; // An important date in world history mHistory.forceRecordAllHistory(); + powerStats.stats = new long[]{0}; + powerStats.uidStats.put(TEST_UID, new long[]{0}); + mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); + mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true); mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG); @@ -87,10 +96,6 @@ public class PowerStatsAggregatorTest { advance(1000); - PowerStats.Descriptor descriptor = - new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, - new PersistableBundle()); - PowerStats powerStats = new PowerStats(descriptor); powerStats.stats = new long[]{10000}; powerStats.uidStats.put(TEST_UID, new long[]{1234}); mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); @@ -181,17 +186,21 @@ public class PowerStatsAggregatorTest { @Test public void incompatiblePowerStats() { + PowerStats.Descriptor descriptor = + new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, + new PersistableBundle()); + PowerStats powerStats = new PowerStats(descriptor); + mHistory.forceRecordAllHistory(); + powerStats.stats = new long[]{0}; + powerStats.uidStats.put(TEST_UID, new long[]{0}); + mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true); mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID, BatteryConsumer.PROCESS_STATE_FOREGROUND); advance(1000); - PowerStats.Descriptor descriptor = - new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, - new PersistableBundle()); - PowerStats powerStats = new PowerStats(descriptor); powerStats.stats = new long[]{10000}; powerStats.uidStats.put(TEST_UID, new long[]{1234}); mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java new file mode 100644 index 000000000000..3c482625b038 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.power.stats; + +import static androidx.test.InstrumentationRegistry.getContext; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; + +import android.os.AggregateBatteryConsumer; +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.BatteryUsageStats; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Parcel; +import android.os.PersistableBundle; +import android.os.UidBatteryConsumer; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.BatteryStatsHistory; +import com.android.internal.os.MonotonicClock; +import com.android.internal.os.PowerProfile; +import com.android.internal.os.PowerStats; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class PowerStatsExporterTest { + + private static final int APP_UID1 = 42; + private static final int APP_UID2 = 84; + private static final double TOLERANCE = 0.01; + + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720) + .setCpuScalingPolicy(0, new int[]{0}, new int[]{100}) + .setAveragePowerForCpuScalingPolicy(0, 360) + .setAveragePowerForCpuScalingStep(0, 0, 300) + .setCpuPowerBracketCount(1) + .setCpuPowerBracket(0, 0, 0); + + private MockClock mClock = new MockClock(); + private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); + private PowerStatsStore mPowerStatsStore; + private PowerStatsAggregator mPowerStatsAggregator; + private BatteryStatsHistory mHistory; + private CpuPowerStatsCollector.CpuStatsArrayLayout mCpuStatsArrayLayout; + private PowerStats.Descriptor mPowerStatsDescriptor; + + @Before + public void setup() { + File storeDirectory = new File(getContext().getCacheDir(), getClass().getSimpleName()); + clearDirectory(storeDirectory); + + AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU) + .trackDeviceStates(AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates(AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE) + .setProcessor( + new CpuAggregatedPowerStatsProcessor(mStatsRule.getPowerProfile(), + mStatsRule.getCpuScalingPolicies())); + + mPowerStatsStore = new PowerStatsStore(storeDirectory, new TestHandler(), config); + mHistory = new BatteryStatsHistory(Parcel.obtain(), storeDirectory, 0, 10000, + mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock, + mMonotonicClock, null); + mPowerStatsAggregator = new PowerStatsAggregator(config, mHistory); + + mCpuStatsArrayLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout(); + mCpuStatsArrayLayout.addDeviceSectionCpuTimeByScalingStep(1); + mCpuStatsArrayLayout.addDeviceSectionCpuTimeByCluster(1); + mCpuStatsArrayLayout.addDeviceSectionPowerEstimate(); + mCpuStatsArrayLayout.addUidSectionCpuTimeByPowerBracket(new int[]{0}); + mCpuStatsArrayLayout.addUidSectionPowerEstimate(); + PersistableBundle extras = new PersistableBundle(); + mCpuStatsArrayLayout.toExtras(extras); + + mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, + mCpuStatsArrayLayout.getDeviceStatsArrayLength(), + mCpuStatsArrayLayout.getUidStatsArrayLength(), extras); + } + + @Test + public void breakdownByProcState_fullRange() throws Exception { + BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( + new String[0], /* includePowerModels */ false, + /* includeProcessStateData */ true, /* powerThreshold */ 0); + exportAggregatedPowerStats(builder, 1000, 10000); + + BatteryUsageStats actual = builder.build(); + String message = "Actual BatteryUsageStats: " + actual; + + assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); + assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); + + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 13.5); + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND, 7.47); + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_BACKGROUND, 6.03); + + assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 12.03); + assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 12.03); + + actual.close(); + } + + @Test + public void breakdownByProcState_subRange() throws Exception { + BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( + new String[0], /* includePowerModels */ false, + /* includeProcessStateData */ true, /* powerThreshold */ 0); + exportAggregatedPowerStats(builder, 3700, 6700); + + BatteryUsageStats actual = builder.build(); + String message = "Actual BatteryUsageStats: " + actual; + + assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 15.4); + assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 15.4); + + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 4.06); + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND, 1.35); + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_BACKGROUND, 2.70); + + assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 11.33); + assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 11.33); + + actual.close(); + } + + @Test + public void combinedProcessStates() throws Exception { + BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( + new String[0], /* includePowerModels */ false, + /* includeProcessStateData */ false, /* powerThreshold */ 0); + exportAggregatedPowerStats(builder, 1000, 10000); + + BatteryUsageStats actual = builder.build(); + String message = "Actual BatteryUsageStats: " + actual; + + assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); + assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); + + assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 13.5); + assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_ANY, 12.03); + UidBatteryConsumer uidScope = actual.getUidBatteryConsumers().stream() + .filter(us -> us.getUid() == APP_UID1).findFirst().orElse(null); + // There shouldn't be any per-procstate data + assertThrows( + IllegalArgumentException.class, + () -> uidScope.getConsumedPower(new BatteryConsumer.Dimensions( + BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND))); + + + actual.close(); + } + + private void recordBatteryHistory() { + PowerStats powerStats = new PowerStats(mPowerStatsDescriptor); + long[] uidStats1 = new long[mCpuStatsArrayLayout.getUidStatsArrayLength()]; + powerStats.uidStats.put(APP_UID1, uidStats1); + long[] uidStats2 = new long[mCpuStatsArrayLayout.getUidStatsArrayLength()]; + powerStats.uidStats.put(APP_UID2, uidStats2); + + mHistory.forceRecordAllHistory(); + + mHistory.startRecordingHistory(1000, 1000, false); + mHistory.recordPowerStats(1000, 1000, powerStats); + mHistory.recordBatteryState(1000, 1000, 70, /* plugged */ false); + mHistory.recordStateStartEvent(1000, 1000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG); + mHistory.recordProcessStateChange(1000, 1000, APP_UID1, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + mHistory.recordProcessStateChange(1000, 1000, APP_UID2, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 11111); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 10000); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 1111); + mHistory.recordPowerStats(1000, 1000, powerStats); + + mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 12345); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 9876); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 2469); + mHistory.recordPowerStats(3000, 3000, powerStats); + + mPowerStatsAggregator.aggregatePowerStats(0, 3500, stats -> { + mPowerStatsStore.storeAggregatedPowerStats(stats); + }); + + mHistory.recordProcessStateChange(4000, 4000, APP_UID1, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + + mHistory.recordStateStopEvent(4000, 4000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG); + + mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 54321); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 14321); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 40000); + mHistory.recordPowerStats(6000, 6000, powerStats); + + mPowerStatsAggregator.aggregatePowerStats(3500, 6500, stats -> { + mPowerStatsStore.storeAggregatedPowerStats(stats); + }); + + mHistory.recordStateStartEvent(7000, 7000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG); + mHistory.recordProcessStateChange(7000, 7000, APP_UID1, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + mHistory.recordProcessStateChange(7000, 7000, APP_UID2, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED); + + mCpuStatsArrayLayout.setTimeByScalingStep(powerStats.stats, 0, 23456); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats1, 0, 23456); + mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 0); + mHistory.recordPowerStats(8000, 8000, powerStats); + + assertThat(mPowerStatsStore.getTableOfContents()).hasSize(2); + } + + private void exportAggregatedPowerStats(BatteryUsageStats.Builder builder, + int monotonicStartTime, int monotonicEndTime) { + recordBatteryHistory(); + PowerStatsExporter exporter = new PowerStatsExporter(mPowerStatsStore, + mPowerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0); + exporter.exportAggregatedPowerStats(builder, monotonicStartTime, monotonicEndTime); + } + + private void assertDevicePowerEstimate(String message, BatteryUsageStats bus, int componentId, + double expected) { + AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); + assertWithMessage(message).that(consumer.getConsumedPower(componentId)) + .isWithin(TOLERANCE).of(expected); + } + + private void assertAllAppsPowerEstimate(String message, BatteryUsageStats bus, int componentId, + double expected) { + AggregateBatteryConsumer consumer = bus.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); + assertWithMessage(message).that(consumer.getConsumedPower(componentId)) + .isWithin(TOLERANCE).of(expected); + } + + private void assertUidPowerEstimate(String message, BatteryUsageStats bus, int uid, + int componentId, int processState, double expected) { + List<UidBatteryConsumer> uidScopes = bus.getUidBatteryConsumers(); + final UidBatteryConsumer uidScope = uidScopes.stream() + .filter(us -> us.getUid() == uid).findFirst().orElse(null); + assertWithMessage(message).that(uidScope).isNotNull(); + assertWithMessage(message).that(uidScope.getConsumedPower( + new BatteryConsumer.Dimensions(componentId, processState))) + .isWithin(TOLERANCE).of(expected); + } + + private void clearDirectory(File dir) { + if (dir.exists()) { + for (File child : dir.listFiles()) { + if (child.isDirectory()) { + clearDirectory(child); + } + child.delete(); + } + } + } + + private static class TestHandler extends Handler { + TestHandler() { + super(Looper.getMainLooper()); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + msg.getCallback().run(); + return true; + } + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java index 0e58787a6a9b..7257a94cbb9a 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java @@ -27,9 +27,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.os.BatteryConsumer; -import android.os.BatteryManager; -import android.os.BatteryUsageStats; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; @@ -59,13 +56,13 @@ public class PowerStatsSchedulerTest { private MockClock mClock = new MockClock(); private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); private MockBatteryStatsImpl mBatteryStats; - private BatteryUsageStatsProvider mBatteryUsageStatsProvider; private PowerStatsScheduler mPowerStatsScheduler; private PowerProfile mPowerProfile; private PowerStatsAggregator mPowerStatsAggregator; private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; @Before + @SuppressWarnings("GuardedBy") public void setup() { final Context context = InstrumentationRegistry.getContext(); @@ -83,11 +80,10 @@ public class PowerStatsSchedulerTest { mPowerProfile = mock(PowerProfile.class); when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0); mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile); - mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats); mPowerStatsAggregator = mock(PowerStatsAggregator.class); mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock, - mMonotonicClock, mHandler, mBatteryStats, mBatteryUsageStatsProvider); + mMonotonicClock, mHandler, mBatteryStats); } @Test @@ -176,70 +172,6 @@ public class PowerStatsSchedulerTest { } @Test - public void storeBatteryUsageStatsOnReset() { - mBatteryStats.forceRecordAllHistory(); - synchronized (mBatteryStats) { - mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true, - BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0); - } - - mPowerStatsScheduler.start(/* schedulePeriodicPowerStatsCollection */false); - - assertThat(mPowerStatsStore.getTableOfContents()).isEmpty(); - - mPowerStatsScheduler.start(true); - - synchronized (mBatteryStats) { - mBatteryStats.noteFlashlightOnLocked(42, mClock.realtime, mClock.uptime); - } - - mClock.realtime += 60000; - mClock.currentTime += 60000; - - synchronized (mBatteryStats) { - mBatteryStats.noteFlashlightOffLocked(42, mClock.realtime, mClock.uptime); - } - - mClock.realtime += 60000; - mClock.currentTime += 60000; - - // Battery stats reset should have the side-effect of saving accumulated battery usage stats - synchronized (mBatteryStats) { - mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); - } - - // Await completion - ConditionVariable done = new ConditionVariable(); - mHandler.post(done::open); - done.block(); - - List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents(); - assertThat(contents).hasSize(1); - - PowerStatsSpan.Metadata metadata = contents.get(0); - - PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(), - BatteryUsageStatsSection.TYPE); - assertThat(span).isNotNull(); - - List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames(); - assertThat(timeFrames).hasSize(1); - assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321); - assertThat(timeFrames.get(0).duration).isEqualTo(120000); - - List<PowerStatsSpan.Section> sections = span.getSections(); - assertThat(sections).hasSize(1); - - PowerStatsSpan.Section section = sections.get(0); - assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE); - BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats(); - assertThat(bus.getAggregateBatteryConsumer( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) - .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) - .isEqualTo(60000); - } - - @Test public void alignToWallClock() { // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15), diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsUidResolverTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsUidResolverTest.java new file mode 100644 index 000000000000..60b2541fa7a8 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsUidResolverTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class PowerStatsUidResolverTest { + + private final PowerStatsUidResolver mResolver = new PowerStatsUidResolver(); + @Mock + PowerStatsUidResolver.Listener mListener; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void addAndRemoveIsolatedUid() { + mResolver.addListener(mListener); + mResolver.noteIsolatedUidAdded(42, 314); + verify(mListener).onIsolatedUidAdded(42, 314); + assertThat(mResolver.mapUid(42)).isEqualTo(314); + + mResolver.noteIsolatedUidRemoved(42, 314); + verify(mListener).onBeforeIsolatedUidRemoved(42, 314); + verify(mListener).onAfterIsolatedUidRemoved(42, 314); + assertThat(mResolver.mapUid(42)).isEqualTo(42); + + verifyNoMoreInteractions(mListener); + } + + @Test + public void retainAndRemoveIsolatedUid() { + mResolver.addListener(mListener); + mResolver.noteIsolatedUidAdded(42, 314); + verify(mListener).onIsolatedUidAdded(42, 314); + assertThat(mResolver.mapUid(42)).isEqualTo(314); + + mResolver.retainIsolatedUid(42); + + mResolver.noteIsolatedUidRemoved(42, 314); + verify(mListener).onBeforeIsolatedUidRemoved(42, 314); + assertThat(mResolver.mapUid(42)).isEqualTo(314); + verifyNoMoreInteractions(mListener); + + mResolver.releaseIsolatedUid(42); + verify(mListener).onAfterIsolatedUidRemoved(42, 314); + assertThat(mResolver.mapUid(42)).isEqualTo(42); + + verifyNoMoreInteractions(mListener); + } + + @Test + public void removeUidsInRange() { + mResolver.noteIsolatedUidAdded(1, 314); + mResolver.noteIsolatedUidAdded(2, 314); + mResolver.noteIsolatedUidAdded(3, 314); + mResolver.noteIsolatedUidAdded(4, 314); + mResolver.noteIsolatedUidAdded(6, 314); + mResolver.noteIsolatedUidAdded(8, 314); + mResolver.noteIsolatedUidAdded(10, 314); + + mResolver.addListener(mListener); + + mResolver.releaseUidsInRange(4, 4); // Single + verify(mListener).onAfterIsolatedUidRemoved(4, 314); + verifyNoMoreInteractions(mListener); + + // Now: [1, 2, 3, 6, 8, 10] + + mResolver.releaseUidsInRange(2, 3); // Inclusive + verify(mListener).onAfterIsolatedUidRemoved(2, 314); + verify(mListener).onAfterIsolatedUidRemoved(3, 314); + verifyNoMoreInteractions(mListener); + + // Now: [1, 6, 8, 10] + + mResolver.releaseUidsInRange(5, 9); // Exclusive + verify(mListener).onAfterIsolatedUidRemoved(6, 314); + verify(mListener).onAfterIsolatedUidRemoved(8, 314); + verifyNoMoreInteractions(mListener); + + // Now: [1, 10] + + mResolver.releaseUidsInRange(5, 9); // Empty + verifyNoMoreInteractions(mListener); + } +} diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java index 1002fba3d60d..88ca02933450 100644 --- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import android.app.ActivityManagerInternal; +import android.app.pinner.PinnedFileStat; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -47,22 +48,19 @@ import com.android.server.wm.ActivityTaskManagerInternal; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.io.BufferedReader; import java.io.CharArrayWriter; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.io.StringReader; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.Optional; +import java.util.List; import java.util.concurrent.TimeUnit; @SmallTest @@ -138,6 +136,13 @@ public class PinnerServiceTest { protected void publishBinderService(PinnerService service, Binder binderService) { // Suppress this for testing, it's not needed and causes conflitcs. } + + @Override + protected PinnerService.PinnedFile pinFileInternal(String fileToPin, + int maxBytesToPin, boolean attemptPinIntrospection) { + return new PinnerService.PinnedFile(-1, + maxBytesToPin, fileToPin, maxBytesToPin); + } }; } @@ -202,20 +207,27 @@ public class PinnerServiceTest { return cw.toString(); } - private int getPinnedSize(PinnerService pinnerService) throws Exception { - return getPinnedSizeImpl(pinnerService, "Total size: "); + private long getPinnedSize(PinnerService pinnerService) { + long totalBytesPinned = 0; + for (PinnedFileStat stat : pinnerService.getPinnerStats()) { + totalBytesPinned += stat.getBytesPinned(); + } + return totalBytesPinned; } - private int getPinnedAnonSize(PinnerService pinnerService) throws Exception { - return getPinnedSizeImpl(pinnerService, "Pinned anon region: "); + private int getPinnedAnonSize(PinnerService pinnerService) { + List<PinnedFileStat> anonStats = pinnerService.getPinnerStats().stream() + .filter(pf -> pf.getGroupName().equals(PinnerService.ANON_REGION_STAT_NAME)) + .toList(); + int totalAnon = 0; + for (PinnedFileStat anonStat : anonStats) { + totalAnon += anonStat.getBytesPinned(); + } + return totalAnon; } - private int getPinnedSizeImpl(PinnerService pinnerService, String sizeToken) throws Exception { - String dumpOutput = getPinnerServiceDump(pinnerService); - BufferedReader bufReader = new BufferedReader(new StringReader(dumpOutput)); - Optional<Integer> size = bufReader.lines().filter(s -> s.contains(sizeToken)) - .map(s -> Integer.valueOf(s.substring(sizeToken.length()))).findAny(); - return size.orElse(-1); + private long getTotalPinnedFiles(PinnerService pinnerService) { + return pinnerService.getPinnerStats().stream().count(); } private void setDeviceConfigPinnedAnonSize(long size) { @@ -227,7 +239,6 @@ public class PinnerServiceTest { } @Test - @Ignore("b/309853498, pinning home app can fail with ENOMEM") public void testPinHomeApp() throws Exception { // Enable HOME app pinning mContext.getOrCreateTestableResources() @@ -245,15 +256,13 @@ public class PinnerServiceTest { ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService); assertThat(pinnedApps.get(KEY_HOME)).isNotNull(); - // Check if dump() reports total pinned bytes - int totalPinnedSizeBytes = getPinnedSize(pinnerService); - assertThat(totalPinnedSizeBytes).isGreaterThan(0); + assertThat(getPinnedSize(pinnerService)).isGreaterThan(0); + assertThat(getTotalPinnedFiles(pinnerService)).isGreaterThan(0); unpinAll(pinnerService); } @Test - @Ignore("b/309853498, pinning home app can fail with ENOMEM") public void testPinHomeAppOnBootCompleted() throws Exception { // Enable HOME app pinning mContext.getOrCreateTestableResources() @@ -271,9 +280,7 @@ public class PinnerServiceTest { ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService); assertThat(pinnedApps.get(KEY_HOME)).isNotNull(); - // Check if dump() reports total pinned bytes - int totalPinnedSizeBytes = getPinnedSize(pinnerService); - assertThat(totalPinnedSizeBytes).isGreaterThan(0); + assertThat(getPinnedSize(pinnerService)).isGreaterThan(0); unpinAll(pinnerService); } @@ -294,12 +301,24 @@ public class PinnerServiceTest { ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService); assertThat(pinnedApps).isEmpty(); - // Check if dump() reports total pinned bytes - int totalPinnedSizeBytes = getPinnedSize(pinnerService); + long totalPinnedSizeBytes = getPinnedSize(pinnerService); assertThat(totalPinnedSizeBytes).isEqualTo(0); int pinnedAnonSizeBytes = getPinnedAnonSize(pinnerService); - assertThat(pinnedAnonSizeBytes).isEqualTo(-1); + assertThat(pinnedAnonSizeBytes).isEqualTo(0); + + unpinAll(pinnerService); + } + + @Test + public void testPinFile() throws Exception { + PinnerService pinnerService = new PinnerService(mContext, mInjector); + pinnerService.onStart(); + + pinnerService.pinFile("test_file", 4096, null, "my_group"); + + assertThat(getPinnedSize(pinnerService)).isGreaterThan(0); + assertThat(getTotalPinnedFiles(pinnerService)).isGreaterThan(0); unpinAll(pinnerService); } @@ -341,11 +360,8 @@ public class PinnerServiceTest { waitForPinnerService(pinnerService); // An empty anon region should clear the associated status entry. pinnedAnonSizeBytes = getPinnedAnonSize(pinnerService); - assertThat(pinnedAnonSizeBytes).isEqualTo(-1); + assertThat(pinnedAnonSizeBytes).isEqualTo(0); unpinAll(pinnerService); } - - // TODO: Add test to check that the pages we expect to be pinned are actually pinned - } diff --git a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java new file mode 100644 index 000000000000..749b07d16ebe --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java @@ -0,0 +1,232 @@ +/* + * Copyright 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.server.audio; + +import static android.media.AudioManager.GET_DEVICES_OUTPUTS; +import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID; +import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4; +import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D; + +import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.media.AudioDeviceInfo; +import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; +import android.media.ILoudnessCodecUpdatesDispatcher; +import android.media.LoudnessCodecInfo; +import android.media.PlayerBase; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.util.Log; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +@RunWith(AndroidJUnit4.class) +@Presubmit +public class LoudnessCodecHelperTest { + private static final String TAG = "LoudnessCodecHelperTest"; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private LoudnessCodecHelper mLoudnessHelper; + + @Mock + private AudioService mAudioService; + @Mock + private ILoudnessCodecUpdatesDispatcher.Default mDispatcher; + + private final int mInitialApcPiid = 1; + + @Before + public void setUp() throws Exception { + mLoudnessHelper = new LoudnessCodecHelper(mAudioService); + + when(mAudioService.getActivePlaybackConfigurations()).thenReturn( + getApcListForPiids(mInitialApcPiid)); + + when(mDispatcher.asBinder()).thenReturn(Mockito.mock(IBinder.class)); + } + + @Test + public void registerDispatcher_sendsInitialUpdateOnStart() throws Exception { + mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); + + mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, + List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_4))); + + verify(mDispatcher).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any()); + } + + @Test + public void unregisterDispatcher_noInitialUpdateOnStart() throws Exception { + mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); + mLoudnessHelper.unregisterLoudnessCodecUpdatesDispatcher(mDispatcher); + + mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, + List.of(getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/false, + CODEC_METADATA_TYPE_MPEG_D))); + + verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + any()); + } + + @Test + public void addCodecInfo_sendsInitialUpdateAfterStart() throws Exception { + mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); + + mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, + List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_4))); + mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, + getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_D)); + + verify(mDispatcher, times(2)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + any()); + } + + @Test + public void addCodecInfoForUnstartedPiid_noUpdateSent() throws Exception { + final int newPiid = 2; + mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); + + mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, + List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_4))); + mLoudnessHelper.addLoudnessCodecInfo(newPiid, + getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_D)); + + verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + any()); + } + + @Test + public void updateCodecParameters_updatesOnlyStartedPiids() throws Exception { + final int newPiid = 2; + mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); + + mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, + List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_4))); + //does not trigger dispatch since active apc list does not contain newPiid + mLoudnessHelper.startLoudnessCodecUpdates(newPiid, + List.of(getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_D))); + verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + any()); + + // triggers dispatch for new active apc with newPiid + mLoudnessHelper.updateCodecParameters(getApcListForPiids(newPiid)); + verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(newPiid), any()); + } + + @Test + public void updateCodecParameters_noStartedPiids_noDispatch() throws Exception { + mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); + mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, + getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true, + CODEC_METADATA_TYPE_MPEG_D)); + + mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid)); + + // no dispatch since mInitialApcPiid was not started + verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + any()); + } + + @Test + public void updateCodecParameters_removedCodecInfo_noDispatch() throws Exception { + final LoudnessCodecInfo info = getLoudnessInfo(/*mediaCodecHash=*/111, + /*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4); + mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); + + mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info)); + mLoudnessHelper.removeLoudnessCodecInfo(mInitialApcPiid, info); + + mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid)); + + // no second dispatch since codec info was removed for updates + verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + any()); + } + + @Test + public void updateCodecParameters_stoppedPiids_noDispatch() throws Exception { + final LoudnessCodecInfo info = getLoudnessInfo(/*mediaCodecHash=*/111, + /*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4); + mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher); + + mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info)); + mLoudnessHelper.stopLoudnessCodecUpdates(mInitialApcPiid); + + mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid)); + + // no second dispatch since piid was removed for updates + verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), + any()); + } + + private List<AudioPlaybackConfiguration> getApcListForPiids(int... piids) { + final ArrayList<AudioPlaybackConfiguration> apcList = new ArrayList<>(); + + AudioDeviceInfo[] devicesStatic = AudioManager.getDevicesStatic(GET_DEVICES_OUTPUTS); + assumeTrue(devicesStatic.length > 0); + int index = new Random().nextInt(devicesStatic.length); + Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + index); + int deviceId = devicesStatic[index].getId(); + + for (int piid : piids) { + PlayerBase.PlayerIdCard idCard = Mockito.mock(PlayerBase.PlayerIdCard.class); + AudioPlaybackConfiguration apc = + new AudioPlaybackConfiguration(idCard, piid, /*uid=*/1, /*pid=*/1); + apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceId); + + apcList.add(apc); + } + return apcList; + } + + private static LoudnessCodecInfo getLoudnessInfo(int mediaCodecHash, boolean isDownmixing, + int metadataType) { + LoudnessCodecInfo info = new LoudnessCodecInfo(); + info.isDownmixing = isDownmixing; + info.mediaCodecHashCode = mediaCodecHash; + info.metadataType = metadataType; + + return info; + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java index 3a3ab84b1c89..dd687fd4286c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java @@ -27,9 +27,9 @@ import static org.mockito.Mockito.when; import android.os.Build; import android.platform.test.annotations.Presubmit; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.compat.PlatformCompat; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.PackageState; import org.junit.Test; diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java index 57b12251c207..d70a4fd555ec 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java @@ -60,7 +60,8 @@ public class UserManagerServiceUserPropertiesTest { .setShowInLauncher(21) .setStartWithParent(false) .setShowInSettings(45) - .setHideInSettingsInQuietMode(false) + .setShowInSharingSurfaces(78) + .setShowInQuietMode(12) .setInheritDevicePolicy(67) .setUseParentsContacts(false) .setCrossProfileIntentFilterAccessControl(10) @@ -74,7 +75,8 @@ public class UserManagerServiceUserPropertiesTest { final UserProperties actualProps = new UserProperties(defaultProps); actualProps.setShowInLauncher(14); actualProps.setShowInSettings(32); - actualProps.setHideInSettingsInQuietMode(true); + actualProps.setShowInSharingSurfaces(46); + actualProps.setShowInQuietMode(27); actualProps.setInheritDevicePolicy(51); actualProps.setUseParentsContacts(true); actualProps.setCrossProfileIntentFilterAccessControl(20); @@ -236,8 +238,10 @@ public class UserManagerServiceUserPropertiesTest { assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher()); assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent()); assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings()); - assertThat(expected.getHideInSettingsInQuietMode()) - .isEqualTo(actual.getHideInSettingsInQuietMode()); + assertThat(expected.getShowInSharingSurfaces()).isEqualTo( + actual.getShowInSharingSurfaces()); + assertThat(expected.getShowInQuietMode()) + .isEqualTo(actual.getShowInQuietMode()); assertThat(expected.getInheritDevicePolicy()).isEqualTo(actual.getInheritDevicePolicy()); assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts()); assertThat(expected.getCrossProfileIntentFilterAccessControl()) diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java index 48eb5c64f3d1..77f693917574 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java @@ -29,6 +29,7 @@ import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; @@ -91,7 +92,8 @@ public class UserManagerServiceUserTypeTest { .setCredentialShareableWithParent(false) .setAuthAlwaysRequiredToDisableQuietMode(true) .setShowInSettings(900) - .setHideInSettingsInQuietMode(true) + .setShowInSharingSurfaces(20) + .setShowInQuietMode(30) .setInheritDevicePolicy(340) .setDeleteAppWithParent(true) .setAlwaysVisible(true); @@ -107,9 +109,9 @@ public class UserManagerServiceUserTypeTest { .setIconBadge(28) .setBadgePlain(29) .setBadgeNoBackground(30) - .setLabel(31) .setMaxAllowedPerParent(32) .setStatusBarIcon(33) + .setLabels(34, 35, 36) .setDefaultRestrictions(restrictions) .setDefaultSystemSettings(systemSettings) .setDefaultSecureSettings(secureSettings) @@ -124,9 +126,11 @@ public class UserManagerServiceUserTypeTest { assertEquals(28, type.getIconBadge()); assertEquals(29, type.getBadgePlain()); assertEquals(30, type.getBadgeNoBackground()); - assertEquals(31, type.getLabel()); assertEquals(32, type.getMaxAllowedPerParent()); assertEquals(33, type.getStatusBarIcon()); + assertEquals(34, type.getLabel(0)); + assertEquals(35, type.getLabel(1)); + assertEquals(36, type.getLabel(2)); assertTrue(UserRestrictionsUtils.areEqual(restrictions, type.getDefaultRestrictions())); assertNotSame(restrictions, type.getDefaultRestrictions()); @@ -164,7 +168,9 @@ public class UserManagerServiceUserTypeTest { assertTrue(type.getDefaultUserPropertiesReference() .isAuthAlwaysRequiredToDisableQuietMode()); assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings()); - assertTrue(type.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); + assertEquals(20, type.getDefaultUserPropertiesReference().getShowInSharingSurfaces()); + assertEquals(30, + type.getDefaultUserPropertiesReference().getShowInQuietMode()); assertEquals(340, type.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); assertTrue(type.getDefaultUserPropertiesReference().getDeleteAppWithParent()); @@ -203,7 +209,7 @@ public class UserManagerServiceUserTypeTest { assertEquals(Resources.ID_NULL, type.getStatusBarIcon()); assertEquals(Resources.ID_NULL, type.getBadgeLabel(0)); assertEquals(Resources.ID_NULL, type.getBadgeColor(0)); - assertEquals(Resources.ID_NULL, type.getLabel()); + assertEquals(Resources.ID_NULL, type.getLabel(0)); assertTrue(type.getDefaultRestrictions().isEmpty()); assertTrue(type.getDefaultSystemSettings().isEmpty()); assertTrue(type.getDefaultSecureSettings().isEmpty()); @@ -222,7 +228,9 @@ public class UserManagerServiceUserTypeTest { assertFalse(props.isCredentialShareableWithParent()); assertFalse(props.getDeleteAppWithParent()); assertFalse(props.getAlwaysVisible()); - assertFalse(props.getHideInSettingsInQuietMode()); + assertEquals(UserProperties.SHOW_IN_LAUNCHER_SEPARATE, props.getShowInSharingSurfaces()); + assertEquals(UserProperties.SHOW_IN_QUIET_MODE_PAUSED, + props.getShowInQuietMode()); assertFalse(type.hasBadge()); } @@ -311,8 +319,9 @@ public class UserManagerServiceUserTypeTest { .setCredentialShareableWithParent(true) .setAuthAlwaysRequiredToDisableQuietMode(false) .setShowInSettings(20) - .setHideInSettingsInQuietMode(false) .setInheritDevicePolicy(21) + .setShowInSharingSurfaces(22) + .setShowInQuietMode(24) .setDeleteAppWithParent(true) .setAlwaysVisible(false); @@ -354,9 +363,11 @@ public class UserManagerServiceUserTypeTest { assertFalse(aospType.getDefaultUserPropertiesReference() .isAuthAlwaysRequiredToDisableQuietMode()); assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings()); - assertFalse(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); assertEquals(21, aospType.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); + assertEquals(22, aospType.getDefaultUserPropertiesReference().getShowInSharingSurfaces()); + assertEquals(24, + aospType.getDefaultUserPropertiesReference().getShowInQuietMode()); assertTrue(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent()); assertFalse(aospType.getDefaultUserPropertiesReference().getAlwaysVisible()); @@ -403,7 +414,10 @@ public class UserManagerServiceUserTypeTest { assertTrue(aospType.getDefaultUserPropertiesReference() .isAuthAlwaysRequiredToDisableQuietMode()); assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings()); - assertTrue(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); + assertEquals(22, + aospType.getDefaultUserPropertiesReference().getShowInSharingSurfaces()); + assertEquals(24, + aospType.getDefaultUserPropertiesReference().getShowInQuietMode()); assertEquals(450, aospType.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); assertFalse(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent()); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 2b6d8eda6bb0..c7d80edc9c2d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -343,6 +343,8 @@ public final class UserManagerTest { assertThat(mainUserId).isEqualTo(parentProfileInfo.id); removeUser(userInfo.id); assertThat(mUserManager.getProfileParent(mainUserId)).isNull(); + assertThat(mUserManager.getProfileLabel()).isEqualTo( + Resources.getSystem().getString(userTypeDetails.getLabel(0))); } @MediumTest diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java index a8e3c7e4ef6d..8464969cd0f3 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java @@ -53,10 +53,10 @@ import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiDevice; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.After; diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java index b2843d82a08a..1bfd43ff60ef 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java @@ -37,10 +37,10 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.servicestests.R; +import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.PackageManagerException; import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Assert; diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java index ad9f920f029d..0f87202f8939 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -29,10 +29,10 @@ import android.util.SparseArray; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.parsing.ParsingPackage; import dalvik.system.DelegateLastClassLoader; import dalvik.system.DexClassLoader; diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java index ebe45a6fa1e8..d00060564e74 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.webkit; +import static android.webkit.Flags.updateServiceV2; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -25,6 +27,9 @@ import android.content.pm.PackageInfo; import android.content.pm.Signature; import android.os.Build; import android.os.Bundle; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.test.suitebuilder.annotation.MediumTest; import android.util.Base64; import android.webkit.UserPackage; @@ -35,6 +40,7 @@ import android.webkit.WebViewProviderResponse; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; @@ -55,11 +61,14 @@ import java.util.concurrent.CountDownLatch; public class WebViewUpdateServiceTest { private final static String TAG = WebViewUpdateServiceTest.class.getSimpleName(); - private WebViewUpdateServiceImpl mWebViewUpdateServiceImpl; + private WebViewUpdateServiceInterface mWebViewUpdateServiceImpl; private TestSystemImpl mTestSystemImpl; private static final String WEBVIEW_LIBRARY_FLAG = "com.android.webview.WebViewLibrary"; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + /** * Creates a new instance. */ @@ -92,8 +101,13 @@ public class WebViewUpdateServiceTest { TestSystemImpl testing = new TestSystemImpl(packages, numRelros, isDebuggable, multiProcessDefault); mTestSystemImpl = Mockito.spy(testing); - mWebViewUpdateServiceImpl = - new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl); + if (updateServiceV2()) { + mWebViewUpdateServiceImpl = + new WebViewUpdateServiceImpl2(null /*Context*/, mTestSystemImpl); + } else { + mWebViewUpdateServiceImpl = + new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl); + } } private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) { @@ -1310,11 +1324,13 @@ public class WebViewUpdateServiceTest { } @Test + @RequiresFlagsDisabled("android.webkit.update_service_v2") public void testMultiProcessEnabledByDefault() { testMultiProcessByDefault(true /* enabledByDefault */); } @Test + @RequiresFlagsDisabled("android.webkit.update_service_v2") public void testMultiProcessDisabledByDefault() { testMultiProcessByDefault(false /* enabledByDefault */); } @@ -1344,6 +1360,7 @@ public class WebViewUpdateServiceTest { } @Test + @RequiresFlagsDisabled("android.webkit.update_service_v2") public void testMultiProcessEnabledByDefaultWithSettingsValue() { testMultiProcessByDefaultWithSettingsValue( true /* enabledByDefault */, Integer.MIN_VALUE, false /* expectEnabled */); @@ -1356,6 +1373,7 @@ public class WebViewUpdateServiceTest { } @Test + @RequiresFlagsDisabled("android.webkit.update_service_v2") public void testMultiProcessDisabledByDefaultWithSettingsValue() { testMultiProcessByDefaultWithSettingsValue( false /* enabledByDefault */, Integer.MIN_VALUE, false /* expectEnabled */); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 213681172882..344a4b0ce324 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -1077,6 +1077,26 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + public void testPackageUninstall_componentNoLongerUserSetList() throws Exception { + final String pkg = "this.is.a.package.name"; + final String component = pkg + "/Ba"; + for (int approvalLevel : new int[] { APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, approvalLevel); + writeExpectedValuesToSettings(approvalLevel); + service.migrateToXml(); + + final String verifyValue = (approvalLevel == APPROVAL_BY_COMPONENT) ? component : pkg; + + assertThat(service.isPackageOrComponentAllowed(verifyValue, 0)).isTrue(); + assertThat(service.isPackageOrComponentUserSet(verifyValue, 0)).isTrue(); + + service.onPackagesChanged(true, new String[]{pkg}, new int[]{103}); + assertThat(service.isPackageOrComponentUserSet(verifyValue, 0)).isFalse(); + } + } + + @Test public void testIsPackageAllowed() { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, 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 b45dcd4d5403..09ffe71a6758 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -241,7 +241,6 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.R; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; -import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag; import com.android.internal.config.sysui.TestableFlagResolver; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.InstanceIdSequenceFake; @@ -5296,7 +5295,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { new Intent(Intent.ACTION_LOCALE_CHANGED)); verify(mZenModeHelper, times(1)).updateDefaultZenRules( - anyInt(), anyBoolean()); + anyInt()); } @Test @@ -8752,7 +8751,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // verify that zen mode helper gets passed in a package name of "android" verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(), - anyInt(), eq(true)); // system call counts as "is system or system ui" + anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call } @Test @@ -8774,7 +8773,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // verify that zen mode helper gets passed in a package name of "android" verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(), - anyInt(), eq(true)); // system call counts as "system or system ui" + anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call } @Test @@ -8795,7 +8794,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // verify that zen mode helper gets passed in the package name from the arg, not the owner verify(mockZenModeHelper).addAutomaticZenRule( eq("another.package"), eq(rule), anyString(), anyInt(), - eq(false)); // doesn't count as a system/systemui call + eq(ZenModeHelper.FROM_APP)); // doesn't count as a system/systemui call } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java index 8f30f413d4d0..6976ec3b0465 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java @@ -82,7 +82,7 @@ public class TestableNotificationManagerService extends NotificationManagerServi } @Override - protected boolean isCallerIsSystemOrSystemUi() { + protected boolean isCallerSystemOrSystemUi() { countSystemChecks++; return isSystemUid || isSystemAppId; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index d4661075a552..cad8bacab8e0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -107,7 +107,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { } @Test - public void testZenPolicyToNotificationPolicy() { + public void testZenPolicyToNotificationPolicy_classic() { ZenModeConfig config = getMutedAllConfig(); config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE; @@ -140,7 +140,101 @@ public class ZenModeConfigTest extends UiServiceTestCase { } @Test + public void testZenPolicyToNotificationPolicy() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + ZenModeConfig config = getMutedAllConfig(); + config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE; + + // Explicitly allow conversations from priority senders to make sure that goes through + // Explicitly disallow channels to make sure that goes through, too + ZenPolicy zenPolicy = new ZenPolicy.Builder() + .allowAlarms(true) + .allowReminders(true) + .allowEvents(true) + .allowConversations(CONVERSATION_SENDERS_IMPORTANT) + .showLights(false) + .showInAmbientDisplay(false) + .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE) + .build(); + + Policy originalPolicy = config.toNotificationPolicy(); + int priorityCategories = originalPolicy.priorityCategories; + int priorityCallSenders = originalPolicy.priorityCallSenders; + int priorityMessageSenders = originalPolicy.priorityMessageSenders; + int priorityConversationsSenders = CONVERSATION_SENDERS_IMPORTANT; + int suppressedVisualEffects = originalPolicy.suppressedVisualEffects; + priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS; + priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS; + priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS; + priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS; + suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS; + suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT; + + Policy expectedPolicy = new Policy(priorityCategories, priorityCallSenders, + priorityMessageSenders, suppressedVisualEffects, + Policy.STATE_PRIORITY_CHANNELS_BLOCKED, priorityConversationsSenders); + assertEquals(expectedPolicy, config.toNotificationPolicy(zenPolicy)); + + // make sure allowChannels=false has gotten through correctly (also covered above) + assertFalse(expectedPolicy.allowPriorityChannels()); + } + + @Test + public void testZenPolicyToNotificationPolicy_unsetChannelsTakesDefault() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + ZenModeConfig config = new ZenModeConfig(); + ZenPolicy zenPolicy = new ZenPolicy.Builder().build(); + + // When allowChannels is not set to anything in the ZenPolicy builder, make sure it takes + // the default value from the zen mode config. + Policy policy = config.toNotificationPolicy(zenPolicy); + assertEquals(config.allowPriorityChannels, policy.allowPriorityChannels()); + } + + @Test + public void testZenConfigToZenPolicy_classic() { + ZenPolicy expected = new ZenPolicy.Builder() + .allowAlarms(true) + .allowReminders(true) + .allowEvents(true) + .showLights(false) + .showBadges(false) + .showInAmbientDisplay(false) + .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS) + .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED) + .allowConversations(ZenPolicy.CONVERSATION_SENDERS_NONE) + .build(); + + ZenModeConfig config = getMutedAllConfig(); + config.allowAlarms = true; + config.allowReminders = true; + config.allowEvents = true; + config.allowCalls = true; + config.allowCallsFrom = Policy.PRIORITY_SENDERS_CONTACTS; + config.allowMessages = true; + config.allowMessagesFrom = Policy.PRIORITY_SENDERS_STARRED; + config.allowConversations = false; + config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE; + config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS; + config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT; + ZenPolicy actual = config.toZenPolicy(); + + assertEquals(expected.getVisualEffectBadge(), actual.getVisualEffectBadge()); + assertEquals(expected.getPriorityCategoryAlarms(), actual.getPriorityCategoryAlarms()); + assertEquals(expected.getPriorityCategoryReminders(), + actual.getPriorityCategoryReminders()); + assertEquals(expected.getPriorityCategoryEvents(), actual.getPriorityCategoryEvents()); + assertEquals(expected.getVisualEffectLights(), actual.getVisualEffectLights()); + assertEquals(expected.getVisualEffectAmbient(), actual.getVisualEffectAmbient()); + assertEquals(expected.getPriorityConversationSenders(), + actual.getPriorityConversationSenders()); + assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders()); + assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders()); + } + + @Test public void testZenConfigToZenPolicy() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); ZenPolicy expected = new ZenPolicy.Builder() .allowAlarms(true) .allowReminders(true) @@ -151,6 +245,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS) .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED) .allowConversations(ZenPolicy.CONVERSATION_SENDERS_NONE) + .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE) .build(); ZenModeConfig config = getMutedAllConfig(); @@ -162,6 +257,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { config.allowMessages = true; config.allowMessagesFrom = Policy.PRIORITY_SENDERS_STARRED; config.allowConversations = false; + config.allowPriorityChannels = false; config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE; config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS; config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT; @@ -178,6 +274,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { actual.getPriorityConversationSenders()); assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders()); assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders()); + assertEquals(expected.getAllowedChannels(), actual.getAllowedChannels()); } @Test @@ -453,7 +550,58 @@ public class ZenModeConfigTest extends UiServiceTestCase { } @Test + public void testZenPolicyXml_classic() throws Exception { + ZenPolicy policy = new ZenPolicy.Builder() + .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS) + .allowMessages(ZenPolicy.PEOPLE_TYPE_NONE) + .allowConversations(ZenPolicy.CONVERSATION_SENDERS_IMPORTANT) + .allowRepeatCallers(true) + .allowAlarms(true) + .allowMedia(false) + .allowSystem(true) + .allowReminders(false) + .allowEvents(true) + .hideAllVisualEffects() + .showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true) + .build(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writePolicyXml(policy, baos); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ZenPolicy fromXml = readPolicyXml(bais); + + assertNotNull(fromXml); + assertEquals(policy.getPriorityCategoryCalls(), fromXml.getPriorityCategoryCalls()); + assertEquals(policy.getPriorityCallSenders(), fromXml.getPriorityCallSenders()); + assertEquals(policy.getPriorityCategoryMessages(), fromXml.getPriorityCategoryMessages()); + assertEquals(policy.getPriorityMessageSenders(), fromXml.getPriorityMessageSenders()); + assertEquals(policy.getPriorityCategoryConversations(), + fromXml.getPriorityCategoryConversations()); + assertEquals(policy.getPriorityConversationSenders(), + fromXml.getPriorityConversationSenders()); + assertEquals(policy.getPriorityCategoryRepeatCallers(), + fromXml.getPriorityCategoryRepeatCallers()); + assertEquals(policy.getPriorityCategoryAlarms(), fromXml.getPriorityCategoryAlarms()); + assertEquals(policy.getPriorityCategoryMedia(), fromXml.getPriorityCategoryMedia()); + assertEquals(policy.getPriorityCategorySystem(), fromXml.getPriorityCategorySystem()); + assertEquals(policy.getPriorityCategoryReminders(), fromXml.getPriorityCategoryReminders()); + assertEquals(policy.getPriorityCategoryEvents(), fromXml.getPriorityCategoryEvents()); + + assertEquals(policy.getVisualEffectFullScreenIntent(), + fromXml.getVisualEffectFullScreenIntent()); + assertEquals(policy.getVisualEffectLights(), fromXml.getVisualEffectLights()); + assertEquals(policy.getVisualEffectPeek(), fromXml.getVisualEffectPeek()); + assertEquals(policy.getVisualEffectStatusBar(), fromXml.getVisualEffectStatusBar()); + assertEquals(policy.getVisualEffectBadge(), fromXml.getVisualEffectBadge()); + assertEquals(policy.getVisualEffectAmbient(), fromXml.getVisualEffectAmbient()); + assertEquals(policy.getVisualEffectNotificationList(), + fromXml.getVisualEffectNotificationList()); + } + + @Test public void testZenPolicyXml() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + ZenPolicy policy = new ZenPolicy.Builder() .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS) .allowMessages(ZenPolicy.PEOPLE_TYPE_NONE) @@ -464,6 +612,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { .allowSystem(true) .allowReminders(false) .allowEvents(true) + .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE) .hideAllVisualEffects() .showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true) .build(); @@ -489,6 +638,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(policy.getPriorityCategorySystem(), fromXml.getPriorityCategorySystem()); assertEquals(policy.getPriorityCategoryReminders(), fromXml.getPriorityCategoryReminders()); assertEquals(policy.getPriorityCategoryEvents(), fromXml.getPriorityCategoryEvents()); + assertEquals(policy.getAllowedChannels(), fromXml.getAllowedChannels()); assertEquals(policy.getVisualEffectFullScreenIntent(), fromXml.getVisualEffectFullScreenIntent()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java index ed7e8ae5c6e5..4e684d0eb036 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -26,8 +26,10 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import android.app.AutomaticZenRule; +import android.app.Flags; import android.content.ComponentName; import android.net.Uri; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.service.notification.Condition; import android.service.notification.ZenDeviceEffects; @@ -43,6 +45,7 @@ import androidx.test.filters.SmallTest; import com.android.server.UiServiceTestCase; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,6 +55,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -60,6 +64,7 @@ import java.util.Set; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class ZenModeDiffTest extends UiServiceTestCase { + // Base set of exempt fields independent of fields that are enabled/disabled via flags. // version is not included in the diff; manual & automatic rules have special handling public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS = Set.of("version", "manualRule", "automaticRules"); @@ -73,6 +78,13 @@ public class ZenModeDiffTest extends UiServiceTestCase { RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL, RuleDiff.FIELD_ZEN_DEVICE_EFFECTS); + // allowPriorityChannels is flagged by android.app.modes_api + public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS = + Set.of("allowPriorityChannels"); + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Test public void testRuleDiff_addRemoveSame() { // Test add, remove, and both sides same @@ -148,6 +160,35 @@ public class ZenModeDiffTest extends UiServiceTestCase { ArrayMap<String, Object> expectedFrom = new ArrayMap<>(); ArrayMap<String, Object> expectedTo = new ArrayMap<>(); List<Field> fieldsForDiff = getFieldsForDiffCheck( + ZenModeConfig.class, getConfigExemptAndFlaggedFields()); + generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo); + + ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2); + assertTrue(d.hasDiff()); + + // Now diff them and check that each of the fields has a diff + for (Field f : fieldsForDiff) { + String name = f.getName(); + assertNotNull("diff not found for field: " + name, d.getDiffForField(name)); + assertTrue(d.getDiffForField(name).hasDiff()); + assertTrue("unexpected field: " + name, expectedFrom.containsKey(name)); + assertTrue("unexpected field: " + name, expectedTo.containsKey(name)); + assertEquals(expectedFrom.get(name), d.getDiffForField(name).from()); + assertEquals(expectedTo.get(name), d.getDiffForField(name).to()); + } + } + + @Test + public void testConfigDiff_fieldDiffs_flagOn() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + // these two start the same + ZenModeConfig c1 = new ZenModeConfig(); + ZenModeConfig c2 = new ZenModeConfig(); + + // maps mapping field name -> expected output value as we set diffs + ArrayMap<String, Object> expectedFrom = new ArrayMap<>(); + ArrayMap<String, Object> expectedTo = new ArrayMap<>(); + List<Field> fieldsForDiff = getFieldsForDiffCheck( ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS); generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo); @@ -231,6 +272,14 @@ public class ZenModeDiffTest extends UiServiceTestCase { assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to()); } + // Helper method that merges the base exempt fields with fields that are flagged + private Set getConfigExemptAndFlaggedFields() { + Set merged = new HashSet(); + merged.addAll(ZEN_MODE_CONFIG_EXEMPT_FIELDS); + merged.addAll(ZEN_MODE_CONFIG_FLAGGED_FIELDS); + return merged; + } + // Helper methods for working with configs, policies, rules // Just makes a zen rule with fields filled in private ZenModeConfig.ZenRule makeRule() { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java index c7905a0e8056..29208f44c64f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java @@ -39,13 +39,17 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.app.Flags; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager.Policy; import android.media.AudioAttributes; import android.os.Bundle; import android.os.UserHandle; +import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.StatusBarNotification; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenPolicy; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -58,6 +62,7 @@ import com.android.server.UiServiceTestCase; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -76,6 +81,9 @@ public class ZenModeFilteringTest extends UiServiceTestCase { private long mTestStartTime; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -526,4 +534,26 @@ public class ZenModeFilteringTest extends UiServiceTestCase { policy, UserHandle.SYSTEM, different, null, 0, 0, 0)); } + + @Test + public void testAllowChannels_priorityPackage() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + // Notification with package priority = PRIORITY_MAX (assigned to indicate canBypassDnd) + NotificationRecord r = getNotificationRecord(); + r.setPackagePriority(Notification.PRIORITY_MAX); + + // Create a policy to allow channels through, which means shouldIntercept is false + ZenModeConfig config = new ZenModeConfig(); + Policy policy = config.toNotificationPolicy(new ZenPolicy.Builder() + .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY) + .build()); + assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); + + // Now create a policy which does not allow priority channels: + policy = config.toNotificationPolicy(new ZenPolicy.Builder() + .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE) + .build()); + assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 37aeb57f728e..97b6b98a0b08 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -53,6 +53,9 @@ import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED; import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG; import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW; import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW; +import static com.android.server.notification.ZenModeHelper.FROM_APP; +import static com.android.server.notification.ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI; +import static com.android.server.notification.ZenModeHelper.FROM_USER; import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE; import static com.google.common.truth.Truth.assertThat; @@ -108,6 +111,7 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Condition; +import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ScheduleInfo; import android.service.notification.ZenModeConfig.ZenRule; @@ -1645,8 +1649,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig config = new ZenModeConfig(); config.automaticRules = new ArrayMap<>(); mZenModeHelper.mConfig = config; - mZenModeHelper.updateDefaultZenRules( - Process.SYSTEM_UID, true); // shouldn't throw null pointer + mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID); // shouldn't throw null pointer mZenModeHelper.pullRules(events); // shouldn't throw null pointer } @@ -1671,7 +1674,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { autoRules.put(SCHEDULE_DEFAULT_RULE_ID, updatedDefaultRule); mZenModeHelper.mConfig.automaticRules = autoRules; - mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID, true); + mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID); assertEquals(updatedDefaultRule, mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID)); } @@ -1697,7 +1700,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { autoRules.put(SCHEDULE_DEFAULT_RULE_ID, updatedDefaultRule); mZenModeHelper.mConfig.automaticRules = autoRules; - mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID, true); + mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID); assertEquals(updatedDefaultRule, mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID)); } @@ -1724,7 +1727,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { autoRules.put(SCHEDULE_DEFAULT_RULE_ID, customDefaultRule); mZenModeHelper.mConfig.automaticRules = autoRules; - mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID, true); + mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID); ZenModeConfig.ZenRule ruleAfterUpdating = mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID); assertEquals(customDefaultRule.enabled, ruleAfterUpdating.enabled); @@ -1748,7 +1751,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // We need the package name to be something that's not "android" so there aren't any // existing rules under that package. String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, false); + CUSTOM_PKG_UID, FROM_APP); assertNotNull(id); } try { @@ -1759,7 +1762,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, false); + CUSTOM_PKG_UID, FROM_APP); fail("allowed too many rules to be created"); } catch (IllegalArgumentException e) { // yay @@ -1780,7 +1783,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, false); + CUSTOM_PKG_UID, FROM_APP); assertNotNull(id); } try { @@ -1791,7 +1794,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, false); + CUSTOM_PKG_UID, FROM_APP); fail("allowed too many rules to be created"); } catch (IllegalArgumentException e) { // yay @@ -1812,7 +1815,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, false); + CUSTOM_PKG_UID, FROM_APP); assertNotNull(id); } try { @@ -1823,7 +1826,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, false); + CUSTOM_PKG_UID, FROM_APP); fail("allowed too many rules to be created"); } catch (IllegalArgumentException e) { // yay @@ -1839,7 +1842,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); @@ -1860,7 +1863,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); @@ -1884,7 +1887,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, false); + CUSTOM_PKG_UID, FROM_APP); mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(), new Condition(zenRule.getConditionId(), "", STATE_TRUE), CUSTOM_PKG_UID, false); @@ -1903,7 +1906,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, false); + CUSTOM_PKG_UID, FROM_APP); AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW", null, @@ -1912,7 +1915,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - mZenModeHelper.updateAutomaticZenRule(id, zenRule2, "", CUSTOM_PKG_UID, false); + mZenModeHelper.updateAutomaticZenRule(id, zenRule2, "", CUSTOM_PKG_UID, FROM_APP); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertEquals("NEW", ruleInConfig.name); @@ -1928,7 +1931,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, false); + CUSTOM_PKG_UID, FROM_APP); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); @@ -1948,7 +1951,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, false); + CUSTOM_PKG_UID, FROM_APP); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); @@ -1972,13 +1975,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", new ComponentName("android", "ScheduleConditionProvider"), sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); Condition condition = new Condition(sharedUri, "", STATE_TRUE); mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true); @@ -2010,6 +2013,182 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + public void addAutomaticZenRule_fromApp_ignoresHiddenEffects() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenDeviceEffects zde = new ZenDeviceEffects.Builder() + .setShouldDisplayGrayscale(true) + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(true) + .setShouldUseNightMode(true) + .setShouldDisableAutoBrightness(true) + .setShouldDisableTapToWake(true) + .setShouldDisableTiltToWake(true) + .setShouldDisableTouch(true) + .setShouldMinimizeRadioUsage(true) + .setShouldMaximizeDoze(true) + .build(); + + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setOwner(OWNER) + .setDeviceEffects(zde) + .build(), + "reasons", 0, FROM_APP); + + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); + assertThat(savedRule.getDeviceEffects()).isEqualTo( + new ZenDeviceEffects.Builder() + .setShouldDisplayGrayscale(true) + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(true) + .setShouldUseNightMode(true) + .build()); + } + + @Test + public void addAutomaticZenRule_fromSystem_respectsHiddenEffects() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenDeviceEffects zde = new ZenDeviceEffects.Builder() + .setShouldDisplayGrayscale(true) + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(true) + .setShouldUseNightMode(true) + .setShouldDisableAutoBrightness(true) + .setShouldDisableTapToWake(true) + .setShouldDisableTiltToWake(true) + .setShouldDisableTouch(true) + .setShouldMinimizeRadioUsage(true) + .setShouldMaximizeDoze(true) + .build(); + + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setOwner(OWNER) + .setDeviceEffects(zde) + .build(), + "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); + assertThat(savedRule.getDeviceEffects()).isEqualTo(zde); + } + + @Test + public void addAutomaticZenRule_fromUser_respectsHiddenEffects() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenDeviceEffects zde = new ZenDeviceEffects.Builder() + .setShouldDisplayGrayscale(true) + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(true) + .setShouldUseNightMode(true) + .setShouldDisableAutoBrightness(true) + .setShouldDisableTapToWake(true) + .setShouldDisableTiltToWake(true) + .setShouldDisableTouch(true) + .setShouldMinimizeRadioUsage(true) + .setShouldMaximizeDoze(true) + .build(); + + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setOwner(OWNER) + .setDeviceEffects(zde) + .build(), + "reasons", 0, FROM_USER); + + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); + assertThat(savedRule.getDeviceEffects()).isEqualTo(zde); + } + + @Test + public void updateAutomaticZenRule_fromApp_preservesPreviousHiddenEffects() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + ZenDeviceEffects original = new ZenDeviceEffects.Builder() + .setShouldDisableTapToWake(true) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setOwner(OWNER) + .setDeviceEffects(original) + .build(), + "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + + ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder() + .setShouldUseNightMode(true) // Good + .setShouldMaximizeDoze(true) // Bad + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setOwner(OWNER) + .setDeviceEffects(updateFromApp) + .build(), + "reasons", 0, FROM_APP); + + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); + assertThat(savedRule.getDeviceEffects()).isEqualTo( + new ZenDeviceEffects.Builder() + .setShouldUseNightMode(true) // From update. + .setShouldDisableTapToWake(true) // From original. + .build()); + } + + @Test + public void updateAutomaticZenRule_fromSystem_updatesHiddenEffects() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + ZenDeviceEffects original = new ZenDeviceEffects.Builder() + .setShouldDisableTapToWake(true) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setOwner(OWNER) + .setDeviceEffects(original) + .build(), + "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + + ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder() + .setShouldUseNightMode(true) // Good + .setShouldMaximizeDoze(true) // Also good + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setDeviceEffects(updateFromSystem) + .build(), + "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); + assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem); + } + + @Test + public void updateAutomaticZenRule_fromUser_updatesHiddenEffects() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + ZenDeviceEffects original = new ZenDeviceEffects.Builder() + .setShouldDisableTapToWake(true) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setOwner(OWNER) + .setDeviceEffects(original) + .build(), + "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + + ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder() + .setShouldUseNightMode(true) // Good + .setShouldMaximizeDoze(true) // Also good + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setDeviceEffects(updateFromUser) + .build(), + "reasons", 0, FROM_USER); + + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); + assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser); + } + + @Test public void testSetManualZenMode() { mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); setupZenConfig(); @@ -2119,7 +2298,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, - "test", Process.SYSTEM_UID, true); + "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE mZenModeHelper.setAutomaticZenRuleState(id, @@ -2128,7 +2307,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Event 2: "User" turns off the automatic rule (sets it to not enabled) zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID, true); + mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID, + FROM_SYSTEM_OR_SYSTEMUI); // Add a new system rule AutomaticZenRule systemRule = new AutomaticZenRule("systemRule", @@ -2138,7 +2318,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule, - "test", Process.SYSTEM_UID, true); + "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // Event 3: turn on the system rule mZenModeHelper.setAutomaticZenRuleState(systemId, @@ -2270,7 +2450,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // Rule 2, same as rule 1 AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", @@ -2280,7 +2460,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // Rule 3, has stricter settings than the default settings ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy(); @@ -2294,7 +2474,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { ruleConfig.toZenPolicy(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // First: turn on rule 1 mZenModeHelper.setAutomaticZenRuleState(id, @@ -2394,7 +2574,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // Rule 2, same as rule 1 but owned by the system AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", @@ -2404,7 +2584,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // Turn on rule 1; call looks like it's from the system. Because setting a condition is // typically an automatic (non-user-initiated) action, expect the calling UID to be @@ -2422,7 +2602,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Disable rule 1. Because this looks like a user action, the UID should not be modified // from the system-provided one. zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID, true); + mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID, + FROM_SYSTEM_OR_SYSTEMUI); // Add a manual rule. Any manual rule changes should not get calling uids reassigned. mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", @@ -2553,7 +2734,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // enable the rule mZenModeHelper.setAutomaticZenRuleState(id, @@ -2596,7 +2777,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { customPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // enable the rule; this will update the consolidated policy mZenModeHelper.setAutomaticZenRuleState(id, @@ -2632,7 +2813,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, true); + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // enable rule 1 mZenModeHelper.setAutomaticZenRuleState(id, @@ -2656,7 +2837,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { customPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, - "test", Process.SYSTEM_UID, true); + "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // enable rule 2; this will update the consolidated policy mZenModeHelper.setAutomaticZenRuleState(id2, @@ -2678,6 +2859,56 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + public void testUpdateConsolidatedPolicy_allowChannels() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + setupZenConfig(); + + // one rule, custom policy, allows channels + ZenPolicy customPolicy = new ZenPolicy.Builder() + .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY) + .build(); + + AutomaticZenRule zenRule = new AutomaticZenRule("name", + null, + new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + customPolicy, + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", + Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + + // enable the rule; this will update the consolidated policy + mZenModeHelper.setAutomaticZenRuleState(id, + new Condition(zenRule.getConditionId(), "", STATE_TRUE), + Process.SYSTEM_UID, true); + + // confirm that channels make it through + assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels()); + + // add new rule with policy that disallows channels + ZenPolicy strictPolicy = new ZenPolicy.Builder() + .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE) + .build(); + + AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", + null, + new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + strictPolicy, + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, + "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + + // enable rule 2; this will update the consolidated policy + mZenModeHelper.setAutomaticZenRuleState(id2, + new Condition(zenRule2.getConditionId(), "", STATE_TRUE), + Process.SYSTEM_UID, true); + + // rule 2 should override rule 1 + assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels()); + } + + @Test public void zenRuleToAutomaticZenRule_allFields() { mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( @@ -2734,7 +2965,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, true); + zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[1]; @@ -2750,7 +2981,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.addCallback(callback); zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true); + mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, + FROM_SYSTEM_OR_SYSTEMUI); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]); @@ -2768,7 +3000,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, false); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, true); + zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[1]; @@ -2784,7 +3016,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.addCallback(callback); zenRule.setEnabled(true); - mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true); + mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, + FROM_SYSTEM_OR_SYSTEMUI); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]); @@ -2803,7 +3036,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, true); + zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[1]; @@ -2839,7 +3072,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, true); + zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[2]; @@ -2879,7 +3112,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, true); + zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[2]; @@ -2919,7 +3152,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, true); + zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE mZenModeHelper.setAutomaticZenRuleState(createdId, @@ -2932,7 +3165,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Event 3: "User" turns off the automatic rule (sets it to not enabled) zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true); + mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, + FROM_SYSTEM_OR_SYSTEMUI); assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java index b4a294d09b7e..2f4f891ce982 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java @@ -16,10 +16,14 @@ package com.android.server.notification; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; +import android.app.Flags; import android.os.Parcel; +import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.ZenPolicy; import android.service.notification.nano.DNDPolicyProto; import android.test.suitebuilder.annotation.SmallTest; @@ -30,6 +34,7 @@ import com.android.server.UiServiceTestCase; import com.google.protobuf.nano.InvalidProtocolBufferNanoException; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,6 +46,9 @@ import java.util.ArrayList; public class ZenPolicyTest extends UiServiceTestCase { private static final String CLASS = "android.service.notification.ZenPolicy"; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Test public void testZenPolicyApplyAllowedToDisallowed() { ZenPolicy.Builder builder = new ZenPolicy.Builder(); @@ -192,6 +200,70 @@ public class ZenPolicyTest extends UiServiceTestCase { } @Test + public void testZenPolicyApplyChannels_applyUnset() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + ZenPolicy unset = builder.build(); + + // priority channels allowed + builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY); + ZenPolicy channelsPriority = builder.build(); + + // unset applied, channels setting keeps its state + channelsPriority.apply(unset); + assertThat(channelsPriority.getAllowedChannels()) + .isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY); + } + + @Test + public void testZenPolicyApplyChannels_applyStricter() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE); + ZenPolicy none = builder.build(); + + builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY); + ZenPolicy priority = builder.build(); + + // priority channels (less strict state) cannot override a setting that sets it to none + none.apply(priority); + assertThat(none.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE); + } + + @Test + public void testZenPolicyApplyChannels_applyLooser() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE); + ZenPolicy none = builder.build(); + + builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY); + ZenPolicy priority = builder.build(); + + // applying a policy with channelType=none overrides priority setting + priority.apply(none); + assertThat(priority.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE); + } + + @Test + public void testZenPolicyApplyChannels_applySet() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + ZenPolicy unset = builder.build(); + + builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY); + ZenPolicy priority = builder.build(); + + // applying a policy with a set channel type actually goes through + unset.apply(priority); + assertThat(unset.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY); + } + + @Test public void testZenPolicyMessagesInvalid() { ZenPolicy.Builder builder = new ZenPolicy.Builder(); @@ -225,6 +297,15 @@ public class ZenPolicyTest extends UiServiceTestCase { } @Test + public void testEmptyZenPolicy_emptyChannels() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + + ZenPolicy policy = builder.build(); + assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET); + } + + @Test public void testAllowReminders() { ZenPolicy.Builder builder = new ZenPolicy.Builder(); @@ -530,6 +611,35 @@ public class ZenPolicyTest extends UiServiceTestCase { } @Test + public void testAllowChannels_noFlag() { + mSetFlagsRule.disableFlags(Flags.FLAG_MODES_API); + + // allowChannels should be unset, not be modifiable, and not show up in any output + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY); + ZenPolicy policy = builder.build(); + + assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET); + assertThat(policy.toString().contains("allowChannels")).isFalse(); + } + + @Test + public void testAllowChannels() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + // allow priority channels + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY); + ZenPolicy policy = builder.build(); + assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY); + + // disallow priority channels + builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE); + policy = builder.build(); + assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE); + } + + @Test public void testTooLongLists_fromParcel() { ArrayList<Integer> longList = new ArrayList<Integer>(50); for (int i = 0; i < 50; i++) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 786432a5dc88..f2e54bc572ae 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1175,10 +1175,12 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testFinishActivityIfPossible_nonVisibleNoAppTransition() { registerTestTransitionPlayer(); + spyOn(mRootWindowContainer.mTransitionController); + final ActivityRecord bottomActivity = createActivityWithTask(); + bottomActivity.setVisibility(false); + bottomActivity.setState(STOPPED, "test"); + bottomActivity.mLastSurfaceShowing = false; final ActivityRecord activity = createActivityWithTask(); - // Put an activity on top of test activity to make it invisible and prevent us from - // accidentally resuming the topmost one again. - new ActivityBuilder(mAtm).build(); activity.setVisibleRequested(false); activity.setState(STOPPED, "test"); @@ -1186,6 +1188,14 @@ public class ActivityRecordTests extends WindowTestsBase { verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE)); assertFalse(activity.inTransition()); + + // finishIfPossible -> completeFinishing -> addToFinishingAndWaitForIdle + // -> resumeFocusedTasksTopActivities + assertTrue(bottomActivity.isState(RESUMED)); + assertTrue(bottomActivity.isVisible()); + verify(mRootWindowContainer.mTransitionController).onVisibleWithoutCollectingTransition( + eq(bottomActivity), any()); + assertTrue(bottomActivity.mLastSurfaceShowing); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index b44d52e97d1c..203475156491 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -201,6 +201,33 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { verify(taskChangeNotifier, never()).notifyActivityDismissingDockedRootTask(); } + /** Verifies that the activity can be destroying after removing task. */ + @Test + public void testRemoveTask() { + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + activity1.setVisible(false); + activity1.finishing = true; + activity1.setState(ActivityRecord.State.STOPPING, "test"); + activity1.addToStopping(false /* scheduleIdle */, false /* idleDelayed */, "test"); + final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + activity2.setState(ActivityRecord.State.RESUMED, "test"); + // The state can happen from ActivityRecord#makeInvisible. + activity2.addToStopping(false /* scheduleIdle */, false /* idleDelayed */, "test"); + mSupervisor.removeTask(activity1.getTask(), true /* killProcess */, + true /* removeFromRecents */, "testRemoveTask"); + mSupervisor.removeTask(activity2.getTask(), true /* killProcess */, + true /* removeFromRecents */, "testRemoveTask"); + + assertEquals(ActivityRecord.State.DESTROYING, activity2.getState()); + assertEquals(ActivityRecord.State.STOPPING, activity1.getState()); + assertTrue(mSupervisor.mStoppingActivities.contains(activity1)); + // Assume that it is called by scheduleIdle from addToStopping. And because + // mStoppingActivities remembers the finishing activity, it can continue to destroy. + mSupervisor.processStoppingAndFinishingActivities(null /* launchedActivity */, + false /* processPausingActivities */, "test"); + assertEquals(ActivityRecord.State.DESTROYING, activity1.getState()); + } + /** Ensures that the calling package name passed to client complies with package visibility. */ @Test public void testFilteredReferred() { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index c6fa8a1b5a98..acce2e2633bd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -51,8 +51,6 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -159,6 +157,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Tests for the {@link DisplayContent} class. @@ -2287,7 +2289,7 @@ public class DisplayContentTests extends WindowTestsBase { 0 /* userId */); dc.mCurrentUniqueDisplayId = mDisplayInfo.uniqueId + "-test"; // Trigger display changed. - dc.onDisplayChanged(); + updateDisplay(dc); // Ensure overridden size and denisty match the most up-to-date values in settings for the // display. verifySizes(dc, forcedWidth, forcedHeight, forcedDensity); @@ -2762,26 +2764,6 @@ public class DisplayContentTests extends WindowTestsBase { mDisplayContent.getKeepClearAreas()); } - @Test - public void testMayImeShowOnLaunchingActivity_negativeWhenSoftInputModeHidden() { - final ActivityRecord app = createActivityRecord(mDisplayContent); - final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, app, "appWin"); - createWindow(null, TYPE_APPLICATION_STARTING, app, "startingWin"); - app.mStartingData = mock(SnapshotStartingData.class); - // Assume the app has shown IME before and warm launching with a snapshot window. - doReturn(true).when(app.mStartingData).hasImeSurface(); - - // Expect true when this IME focusable activity will show IME during launching. - assertTrue(WindowManager.LayoutParams.mayUseInputMethod(appWin.mAttrs.flags)); - assertTrue(mDisplayContent.mayImeShowOnLaunchingActivity(app)); - - // Not expect IME will be shown during launching if the app's softInputMode is hidden. - appWin.mAttrs.softInputMode = SOFT_INPUT_STATE_ALWAYS_HIDDEN; - assertFalse(mDisplayContent.mayImeShowOnLaunchingActivity(app)); - appWin.mAttrs.softInputMode = SOFT_INPUT_STATE_HIDDEN; - assertFalse(mDisplayContent.mayImeShowOnLaunchingActivity(app)); - } - private void removeRootTaskTests(Runnable runnable) { final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, @@ -2852,7 +2834,7 @@ public class DisplayContentTests extends WindowTestsBase { */ private DisplayContent createDisplayNoUpdateDisplayInfo() { final DisplayContent displayContent = createNewDisplay(); - doNothing().when(displayContent).updateDisplayInfo(); + doNothing().when(displayContent).updateDisplayInfo(any()); return displayContent; } @@ -2882,6 +2864,16 @@ public class DisplayContentTests extends WindowTestsBase { return result; } + private void updateDisplay(DisplayContent displayContent) { + CompletableFuture<Object> future = new CompletableFuture<>(); + displayContent.requestDisplayUpdate(() -> future.complete(new Object())); + try { + future.get(15, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } + private void tapOnDisplay(final DisplayContent dc) { final DisplayMetrics dm = dc.getDisplayMetrics(); final float x = dm.widthPixels / 2; diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index e7ac33fc6a1a..76018683c987 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -30,6 +30,7 @@ import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_90; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -130,13 +131,17 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { }); mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy( mDisplayContent, mMockHandler); + + // Do not show the real toast. + spyOn(mDisplayRotationCompatPolicy); + doNothing().when(mDisplayRotationCompatPolicy).showToast(anyInt()); + doNothing().when(mDisplayRotationCompatPolicy).showToast(anyInt(), anyString()); } @Test public void testOpenedCameraInSplitScreen_showToast() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); spyOn(mTask); - spyOn(mDisplayRotationCompatPolicy); doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode(); doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode(); @@ -160,7 +165,6 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { public void testOpenedCameraInSplitScreen_orientationNotFixed_doNotShowToast() { configureActivity(SCREEN_ORIENTATION_UNSPECIFIED); spyOn(mTask); - spyOn(mDisplayRotationCompatPolicy); doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode(); doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode(); @@ -175,7 +179,6 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { public void testOnScreenRotationAnimationFinished_treatmentNotEnabled_doNotShowToast() { when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) .thenReturn(false); - spyOn(mDisplayRotationCompatPolicy); mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished(); @@ -185,8 +188,6 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { @Test public void testOnScreenRotationAnimationFinished_noOpenCamera_doNotShowToast() { - spyOn(mDisplayRotationCompatPolicy); - mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished(); verify(mDisplayRotationCompatPolicy, never()).showToast( @@ -197,7 +198,6 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { public void testOnScreenRotationAnimationFinished_notFullscreen_doNotShowToast() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); doReturn(true).when(mActivity).inMultiWindowMode(); - spyOn(mDisplayRotationCompatPolicy); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); @@ -211,7 +211,6 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { public void testOnScreenRotationAnimationFinished_orientationNotFixed_doNotShowToast() { configureActivity(SCREEN_ORIENTATION_UNSPECIFIED); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - spyOn(mDisplayRotationCompatPolicy); mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished(); @@ -223,7 +222,6 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { public void testOnScreenRotationAnimationFinished_showToast() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - spyOn(mDisplayRotationCompatPolicy); mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished(); diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index 2b19ad97e90c..1be61c36f272 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; @@ -114,8 +115,7 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mDisplayUniqueId = "test:" + sNextUniqueId++; mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500) .setUniqueId(mDisplayUniqueId).build(); - when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId))) - .thenReturn(mTestDisplay); + doReturn(mTestDisplay).when(mRootWindowContainer).getDisplayContent(mDisplayUniqueId); Task rootTask = mTestDisplay.getDefaultTaskDisplayArea() .createRootTask(TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true); @@ -200,7 +200,7 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { public void testReturnsEmptyDisplayIfDisplayIsNotFound() { mTarget.saveTask(mTestTask); - when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId))).thenReturn(null); + doReturn(null).when(mRootWindowContainer).getDisplayContent(eq(mDisplayUniqueId)); mTarget.getLaunchParams(mTestTask, null, mResult); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index eb78906f570d..5a434988e548 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -317,6 +317,31 @@ public class RootWindowContainerTests extends WindowTestsBase { assertTrue(firstActivity.mRequestForceTransition); } + @Test + public void testMultipleActivitiesTaskEnterPip() { + // Enable shell transition because the order of setting windowing mode is different. + registerTestTransitionPlayer(); + final ActivityRecord transientActivity = new ActivityBuilder(mAtm) + .setCreateTask(true).build(); + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final ActivityRecord activity2 = new ActivityBuilder(mAtm) + .setTask(activity1.getTask()).build(); + activity2.setState(RESUMED, "test"); + + // Assume the top activity switches to a transient-launch, e.g. recents. + transientActivity.setState(RESUMED, "test"); + transientActivity.getTask().moveToFront("test"); + + mRootWindowContainer.moveActivityToPinnedRootTask(activity2, + null /* launchIntoPipHostActivity */, "test"); + assertEquals("Created PiP task must not change focus", transientActivity.getTask(), + mRootWindowContainer.getTopDisplayFocusedRootTask()); + final Task newPipTask = activity2.getTask(); + assertEquals(newPipTask, mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask()); + assertNotEquals(newPipTask, activity1.getTask()); + assertFalse("Created PiP task must not be in recents", newPipTask.inRecents); + } + /** * When there is only one activity in the Task, and the activity is requesting to enter PIP, the * whole Task should enter PIP. diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 9af5ba57d6c9..b1def8d0b3a6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -38,6 +38,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.nullable; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; @@ -57,7 +58,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; import android.hardware.devicestate.DeviceStateManager; -import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; import android.net.Uri; import android.os.Handler; @@ -69,6 +70,7 @@ import android.os.StrictMode; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.Log; +import android.view.DisplayInfo; import android.view.InputChannel; import android.view.SurfaceControl; @@ -101,6 +103,7 @@ import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.util.ArrayList; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -239,6 +242,12 @@ public class SystemServicesTestRule implements TestRule { doNothing().when(contentResolver) .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class), anyInt()); + + // Unit test should not register listener to the real service. + final DisplayManagerGlobal dmg = DisplayManagerGlobal.getInstance(); + spyOn(dmg); + doNothing().when(dmg).registerDisplayListener( + any(), any(Executor.class), anyLong(), anyString()); } private void setUpLocalServices() { @@ -267,6 +276,12 @@ public class SystemServicesTestRule implements TestRule { // DisplayManagerInternal final DisplayManagerInternal dmi = mock(DisplayManagerInternal.class); doReturn(dmi).when(() -> LocalServices.getService(eq(DisplayManagerInternal.class))); + doAnswer(invocation -> { + int displayId = invocation.getArgument(0); + DisplayInfo displayInfo = invocation.getArgument(1); + mWmService.mRoot.getDisplayContent(displayId).getDisplay().getDisplayInfo(displayInfo); + return null; + }).when(dmi).getNonOverrideDisplayInfo(anyInt(), any()); // ColorDisplayServiceInternal final ColorDisplayService.ColorDisplayServiceInternal cds = @@ -376,9 +391,6 @@ public class SystemServicesTestRule implements TestRule { mWmService.onInitReady(); mAtmService.setWindowManager(mWmService); - // Avoid real display events interfering with the test. Also avoid leaking registration. - mContext.getSystemService(DisplayManager.class) - .unregisterDisplayListener(mAtmService.mRootWindowContainer); mWmService.mDisplayEnabled = true; mWmService.mDisplayReady = true; mAtmService.getTransitionController().mIsWaitingForDisplayEnabled = false; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java index 2d5b72b3c680..d183cf720491 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java @@ -60,6 +60,11 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + /** * Build/Install/Run: * atest WmTests:WindowContextListenerControllerTests @@ -309,7 +314,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { return null; }).when(display).getDisplayInfo(any(DisplayInfo.class)); - mContainer.getDisplayContent().onDisplayChanged(); + updateDisplay(mContainer.getDisplayContent()); assertThat(mockToken.mConfiguration).isEqualTo(config1); assertThat(mockToken.mDisplayId).isEqualTo(mContainer.getDisplayContent().getDisplayId()); @@ -352,4 +357,14 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { mRemoved = true; } } + + private void updateDisplay(DisplayContent displayContent) { + CompletableFuture<Object> future = new CompletableFuture<>(); + displayContent.requestDisplayUpdate(() -> future.complete(new Object())); + try { + future.get(15, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index d8a9a282a622..2007f680f1ae 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -757,7 +757,6 @@ public class WindowStateTests extends WindowTestsBase { startingApp.getWindowFrames().setInsetsChanged(true); startingApp.updateResizingWindowIfNeeded(); assertTrue(startingApp.isDrawn()); - assertFalse(startingApp.getOrientationChanging()); } @SetupWindows(addWindows = W_ABOVE_ACTIVITY) diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index b77596391be1..7239ba9cd07e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -24,6 +24,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.content.ComponentName; @@ -1565,6 +1566,33 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public boolean setSandboxedDetectionTrainingDataOp(int opMode) { + synchronized (this) { + enforceIsCallerPreinstalledAssistant(); + + if (mImpl == null) { + Slog.w(TAG, "setSandboxedDetectionTrainingDataop without running" + + " voice interaction service"); + return false; + } + + int callingUid = Binder.getCallingUid(); + final long caller = Binder.clearCallingIdentity(); + try { + AppOpsManager appOpsManager = (AppOpsManager) + mContext.getSystemService(Context.APP_OPS_SERVICE); + appOpsManager.setUidMode( + AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, + callingUid, opMode); + } finally { + Binder.restoreCallingIdentity(caller); + } + + return true; + } + } + //----------------- Model management APIs --------------------------------// @Override @@ -2219,6 +2247,13 @@ public class VoiceInteractionManagerService extends SystemService { } } + private void enforceIsCallerPreinstalledAssistant() { + if (!isCallerPreinstalledAssistant()) { + throw new + SecurityException("Caller is not the pre-installed assistant."); + } + } + private void enforceCallerAllowedToEnrollVoiceModel() { if (isCallerHoldingPermission(Manifest.permission.KEYPHRASE_ENROLLMENT_APPLICATION)) { return; @@ -2233,6 +2268,13 @@ public class VoiceInteractionManagerService extends SystemService { && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid(); } + private boolean isCallerPreinstalledAssistant() { + return mImpl != null + && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid() + && (mImpl.mInfo.getServiceInfo().applicationInfo.isSystemApp() + || mImpl.mInfo.getServiceInfo().applicationInfo.isUpdatedSystemApp()); + } + private void setImplLocked(VoiceInteractionManagerServiceImpl impl) { mImpl = impl; mAtmInternal.notifyActiveVoiceInteractionServiceChanged( diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 13a045858ab1..bbd01d6bcd67 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -289,6 +289,11 @@ public abstract class InCallService extends Service { switch (msg.what) { case MSG_SET_IN_CALL_ADAPTER: + if (mPhone != null) { + Log.i(this, "mPhone is already instantiated, ignoring " + + "request to reset adapter."); + break; + } String callingPackage = getApplicationContext().getOpPackageName(); mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage, getApplicationContext().getApplicationInfo().targetSdkVersion); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index c124079ca2e3..ede4885df097 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8853,6 +8853,24 @@ public class CarrierConfigManager { KEY_PREFIX + "epdg_static_address_roaming_string"; /** + * Controls if the multiple SA proposals allowed for IKE session to include + * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple + * IKE SA proposals as per RFC 7296. + */ + @FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS) + public static final String KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL = + KEY_PREFIX + "supports_ike_session_multiple_sa_proposals_bool"; + + /** + * Controls if the multiple SA proposals allowed for Child session to include + * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple + * Child SA proposals as per RFC 7296. + */ + @FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS) + public static final String KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL = + KEY_PREFIX + "supports_child_session_multiple_sa_proposals_bool"; + + /** * List of supported key sizes for AES Cipher Block Chaining (CBC) encryption mode of child * session. Possible values are: * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, @@ -9187,6 +9205,8 @@ public class CarrierConfigManager { defaults.putInt(KEY_IKE_REKEY_HARD_TIMER_SEC_INT, 14400); defaults.putInt(KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT, 3600); defaults.putInt(KEY_CHILD_SA_REKEY_HARD_TIMER_SEC_INT, 7200); + defaults.putBoolean(KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL, false); + defaults.putBoolean(KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL, false); defaults.putIntArray( KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY, new int[] {500, 1000, 2000, 4000, 8000}); defaults.putInt(KEY_DPD_TIMER_SEC_INT, 120); diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java index 4548a7df6874..1e5f33f26dc2 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java @@ -710,9 +710,15 @@ public class GraphicsActivity extends Activity { float childFrameRate = Collections.max(frameRates); float parentFrameRate = childFrameRate / 2; int initialNumEvents = mModeChangedEvents.size(); - parent.setFrameRate(parentFrameRate); parent.setFrameRateSelectionStrategy(parentStrategy); - child.setFrameRate(childFrameRate); + + // For Self case, we want to test that child gets default behavior + if (parentStrategy == SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF) { + parent.setFrameRateCategory(Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE); + } else { + parent.setFrameRate(parentFrameRate); + child.setFrameRate(childFrameRate); + } // Verify float expectedFrameRate = diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java index bed9cff75e1d..29f6879f37c3 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java @@ -16,11 +16,8 @@ package android.view.surfacecontroltests; -import static org.junit.Assume.assumeTrue; - import android.Manifest; import android.hardware.display.DisplayManager; -import android.os.SystemProperties; import android.support.test.uiautomator.UiDevice; import android.view.Surface; import android.view.SurfaceControl; @@ -54,10 +51,6 @@ public class SurfaceControlTest { UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); - // TODO(b/290634611): clean this up once SF new front end is enabled by default - assumeTrue(SystemProperties.getBoolean( - "persist.debug.sf.enable_layer_lifecycle_manager", false)); - uiDevice.wakeUp(); uiDevice.executeShellCommand("wm dismiss-keyguard"); @@ -118,10 +111,11 @@ public class SurfaceControlTest { } @Test - public void testSurfaceControlFrameRateSelectionStrategySelf() throws InterruptedException { + public void testSurfaceControlFrameRateSelectionStrategyPropagate() + throws InterruptedException { GraphicsActivity activity = mActivityRule.getActivity(); activity.testSurfaceControlFrameRateSelectionStrategy( - SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF); + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE); } @Test @@ -131,4 +125,12 @@ public class SurfaceControlTest { activity.testSurfaceControlFrameRateSelectionStrategy( SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } + + @Test + public void testSurfaceControlFrameRateSelectionStrategySelf() + throws InterruptedException { + GraphicsActivity activity = mActivityRule.getActivity(); + activity.testSurfaceControlFrameRateSelectionStrategy( + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF); + } } diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index 9c2899ac6087..3aee93296eee 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -20,6 +20,8 @@ import android.app.Instrumentation import android.app.WallpaperManager import android.content.res.Resources import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.Region +import android.tools.common.flicker.subject.layers.LayersTraceSubject import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS import android.tools.common.traces.component.ComponentNameMatcher import android.tools.common.traces.component.ComponentNameMatcher.Companion.SPLASH_SCREEN @@ -125,27 +127,19 @@ class TaskTransitionTest(flicker: LegacyFlickerTest) : BaseTest(flicker) { val backgroundColorLayer = ComponentNameMatcher("", "animation-background") val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) flicker.assertLayers { - this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") { - it.visibleRegion(launchNewTaskApp.componentMatcher).coversExactly(displayBounds) - } + visibleRegionCovers(launchNewTaskApp.componentMatcher, displayBounds) .isInvisible(backgroundColorLayer) - .hasNoColor(backgroundColorLayer) .then() // Transitioning .isVisible(backgroundColorLayer) - .hasColor(backgroundColorLayer) .then() // Fully transitioned to simple SIMPLE_ACTIVITY - .invoke( - "SIMPLE_ACTIVITY's splashscreen coversExactly displayBounds", + .visibleRegionCovers( + ComponentSplashScreenMatcher(simpleApp.componentMatcher), + displayBounds, isOptional = true - ) { - it.visibleRegion(ComponentSplashScreenMatcher(simpleApp.componentMatcher)) - .coversExactly(displayBounds) - } - .invoke("SIMPLE_ACTIVITY coversExactly displayBounds") { - it.visibleRegion(simpleApp.componentMatcher).coversExactly(displayBounds) - } + ) + .visibleRegionCovers(simpleApp.componentMatcher, displayBounds) .isInvisible(backgroundColorLayer) .hasNoColor(backgroundColorLayer) .then() @@ -154,18 +148,12 @@ class TaskTransitionTest(flicker: LegacyFlickerTest) : BaseTest(flicker) { .hasColor(backgroundColorLayer) .then() // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY - .invoke( - "LAUNCH_NEW_TASK_ACTIVITY's splashscreen coversExactly displayBounds", + .visibleRegionCovers( + ComponentSplashScreenMatcher(launchNewTaskApp.componentMatcher), + displayBounds, isOptional = true - ) { - it.visibleRegion( - ComponentSplashScreenMatcher(launchNewTaskApp.componentMatcher) - ) - .coversExactly(displayBounds) - } - .invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") { - it.visibleRegion(launchNewTaskApp.componentMatcher).coversExactly(displayBounds) - } + ) + .visibleRegionCovers(launchNewTaskApp.componentMatcher, displayBounds) .isInvisible(backgroundColorLayer) .hasNoColor(backgroundColorLayer) } @@ -223,6 +211,14 @@ class TaskTransitionTest(flicker: LegacyFlickerTest) : BaseTest(flicker) { return ComponentNameMatcher(rawComponentMatcher.className) } + private fun LayersTraceSubject.visibleRegionCovers( + component: IComponentMatcher, + expectedArea: Region, + isOptional: Boolean = true + ): LayersTraceSubject = invoke("$component coversExactly $expectedArea", isOptional) { + it.visibleRegion(component).coversExactly(expectedArea) + } + @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams() = LegacyFlickerTestFactory.nonRotationTests() diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java index d075b5f01ef9..5434c82b07bd 100644 --- a/tests/Input/src/com/android/test/input/InputDeviceTest.java +++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java @@ -51,6 +51,7 @@ public class InputDeviceTest { assertEquals(device.getName(), outDevice.getName()); assertEquals(device.getVendorId(), outDevice.getVendorId()); assertEquals(device.getProductId(), outDevice.getProductId()); + assertEquals(device.getDeviceBus(), outDevice.getDeviceBus()); assertEquals(device.getDescriptor(), outDevice.getDescriptor()); assertEquals(device.isExternal(), outDevice.isExternal()); assertEquals(device.getSources(), outDevice.getSources()); @@ -79,6 +80,7 @@ public class InputDeviceTest { .setName("Test Device " + DEVICE_ID) .setVendorId(44) .setProductId(45) + .setDeviceBus(3) .setDescriptor("descriptor") .setExternal(true) .setSources(InputDevice.SOURCE_HDMI) diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh index 85038be80c51..91e6814ed243 100755 --- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh +++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh @@ -51,6 +51,8 @@ ANNOTATION_FILTER=$TEMP/annotation-filter.txt HOSTSTUBGEN_OUT=$TEMP/output.txt +EXTRA_ARGS="" + # Because of `set -e`, we can't return non-zero from functions, so we store # HostStubGen result in it. HOSTSTUBGEN_RC=0 @@ -115,6 +117,7 @@ run_hoststubgen() { --keep-static-initializer-annotation \ android.hosttest.annotation.HostSideTestStaticInitializerKeep \ $filter_arg \ + $EXTRA_ARGS \ |& tee $HOSTSTUBGEN_OUT HOSTSTUBGEN_RC=${PIPESTATUS[0]} echo "HostStubGen exited with $HOSTSTUBGEN_RC" @@ -209,7 +212,6 @@ com.supported.* com.unsupported.* " - run_hoststubgen_for_failure "One specific class disallowed" \ "TinyFrameworkClassAnnotations is not allowed to have Ravenwood annotations" \ " @@ -229,6 +231,14 @@ IMPL="" run_hoststubgen_for_success "No impl generation" "" STUB="" IMPL="" run_hoststubgen_for_success "No stub, no impl generation" "" +EXTRA_ARGS="--in-jar abc" run_hoststubgen_for_failure "Duplicate arg" \ + "Duplicate or conflicting argument found: --in-jar" \ + "" + +EXTRA_ARGS="--quiet" run_hoststubgen_for_failure "Conflicting arg" \ + "Duplicate or conflicting argument found: --quiet" \ + "" + echo "All tests passed" exit 0
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 3cdddc23b332..dbcf3a5207e5 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -22,7 +22,6 @@ import com.android.hoststubgen.filters.ConstantFilter import com.android.hoststubgen.filters.DefaultHookInjectingFilter import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.filters.ImplicitOutputFilter -import com.android.hoststubgen.filters.KeepAllClassesFilter import com.android.hoststubgen.filters.OutputFilter import com.android.hoststubgen.filters.StubIntersectingFilter import com.android.hoststubgen.filters.createFilterFromTextPolicyFile @@ -52,15 +51,15 @@ class HostStubGen(val options: HostStubGenOptions) { val errors = HostStubGenErrors() // Load all classes. - val allClasses = loadClassStructures(options.inJar) + val allClasses = loadClassStructures(options.inJar.get) // Dump the classes, if specified. - options.inputJarDumpFile?.let { + options.inputJarDumpFile.ifSet { PrintWriter(it).use { pw -> allClasses.dump(pw) } log.i("Dump file created at $it") } - options.inputJarAsKeepAllFile?.let { + options.inputJarAsKeepAllFile.ifSet { PrintWriter(it).use { pw -> allClasses.forEach { classNode -> printAsTextPolicy(pw, classNode) @@ -74,11 +73,11 @@ class HostStubGen(val options: HostStubGenOptions) { // Transform the jar. convert( - options.inJar, - options.outStubJar, - options.outImplJar, + options.inJar.get, + options.outStubJar.get, + options.outImplJar.get, filter, - options.enableClassChecker, + options.enableClassChecker.get, allClasses, errors, ) @@ -153,7 +152,7 @@ class HostStubGen(val options: HostStubGenOptions) { // text-file based filter, which is handled by parseTextFilterPolicyFile. // The first filter is for the default policy from the command line options. - var filter: OutputFilter = ConstantFilter(options.defaultPolicy, "default-by-options") + var filter: OutputFilter = ConstantFilter(options.defaultPolicy.get, "default-by-options") // Next, we need a filter that resolves "class-wide" policies. // This is used when a member (methods, fields, nested classes) don't get any polices @@ -163,16 +162,16 @@ class HostStubGen(val options: HostStubGenOptions) { // Inject default hooks from options. filter = DefaultHookInjectingFilter( - options.defaultClassLoadHook, - options.defaultMethodCallHook, + options.defaultClassLoadHook.get, + options.defaultMethodCallHook.get, filter ) - val annotationAllowedClassesFilter = options.annotationAllowedClassesFile.let { filename -> - if (filename == null) { + val annotationAllowedClassesFilter = options.annotationAllowedClassesFile.get.let { file -> + if (file == null) { ClassFilter.newNullFilter(true) // Allow all classes } else { - ClassFilter.loadFromFile(filename, false) + ClassFilter.loadFromFile(file, false) } } @@ -196,7 +195,7 @@ class HostStubGen(val options: HostStubGenOptions) { // Next, "text based" filter, which allows to override polices without touching // the target code. - options.policyOverrideFile?.let { + options.policyOverrideFile.ifSet { filter = createFilterFromTextPolicyFile(it, allClasses, filter) } @@ -212,11 +211,6 @@ class HostStubGen(val options: HostStubGenOptions) { // Apply the implicit filter. filter = ImplicitOutputFilter(errors, allClasses, filter) - // Optionally keep all classes. - if (options.keepAllClasses) { - filter = KeepAllClassesFilter(filter) - } - return filter } @@ -422,9 +416,9 @@ class HostStubGen(val options: HostStubGenOptions) { outVisitor = CheckClassAdapter(outVisitor) } val visitorOptions = BaseAdapter.Options( - enablePreTrace = options.enablePreTrace, - enablePostTrace = options.enablePostTrace, - enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection, + enablePreTrace = options.enablePreTrace.get, + enablePostTrace = options.enablePostTrace.get, + enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection.get, errors = errors, ) outVisitor = BaseAdapter.getVisitor(classInternalName, classes, outVisitor, filter, diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index 83f873d38f1b..0ae52afb73e4 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -21,21 +21,60 @@ import java.io.File import java.io.FileReader /** + * A single value that can only set once. + */ +class SetOnce<T>( + private var value: T, +) { + class SetMoreThanOnceException : Exception() + + private var set = false + + fun set(v: T) { + if (set) { + throw SetMoreThanOnceException() + } + if (v == null) { + throw NullPointerException("This shouldn't happen") + } + set = true + value = v + } + + val get: T + get() = this.value + + val isSet: Boolean + get() = this.set + + fun <R> ifSet(block: (T & Any) -> R): R? { + if (isSet) { + return block(value!!) + } + return null + } + + override fun toString(): String { + return "$value" + } +} + +/** * Options that can be set from command line arguments. */ class HostStubGenOptions( /** Input jar file*/ - var inJar: String = "", + var inJar: SetOnce<String> = SetOnce(""), /** Output stub jar file */ - var outStubJar: String? = null, + var outStubJar: SetOnce<String?> = SetOnce(null), /** Output implementation jar file */ - var outImplJar: String? = null, + var outImplJar: SetOnce<String?> = SetOnce(null), - var inputJarDumpFile: String? = null, + var inputJarDumpFile: SetOnce<String?> = SetOnce(null), - var inputJarAsKeepAllFile: String? = null, + var inputJarAsKeepAllFile: SetOnce<String?> = SetOnce(null), var stubAnnotations: MutableSet<String> = mutableSetOf(), var keepAnnotations: MutableSet<String> = mutableSetOf(), @@ -51,27 +90,26 @@ class HostStubGenOptions( var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(), - var annotationAllowedClassesFile: String? = null, + var annotationAllowedClassesFile: SetOnce<String?> = SetOnce(null), - var defaultClassLoadHook: String? = null, - var defaultMethodCallHook: String? = null, + var defaultClassLoadHook: SetOnce<String?> = SetOnce(null), + var defaultMethodCallHook: SetOnce<String?> = SetOnce(null), var intersectStubJars: MutableSet<String> = mutableSetOf(), - var policyOverrideFile: String? = null, + var policyOverrideFile: SetOnce<String?> = SetOnce(null), - var defaultPolicy: FilterPolicy = FilterPolicy.Remove, - var keepAllClasses: Boolean = false, + var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove), - var logLevel: LogLevel = LogLevel.Info, + var logLevel: SetOnce<LogLevel> = SetOnce(LogLevel.Info), - var cleanUpOnError: Boolean = false, + var cleanUpOnError: SetOnce<Boolean> = SetOnce(true), - var enableClassChecker: Boolean = false, - var enablePreTrace: Boolean = false, - var enablePostTrace: Boolean = false, + var enableClassChecker: SetOnce<Boolean> = SetOnce(false), + var enablePreTrace: SetOnce<Boolean> = SetOnce(false), + var enablePostTrace: SetOnce<Boolean> = SetOnce(false), - var enableNonStubMethodCallDetection: Boolean = false, + var enableNonStubMethodCallDetection: SetOnce<Boolean> = SetOnce(false), ) { companion object { @@ -111,110 +149,120 @@ class HostStubGenOptions( break } - when (arg) { - // TODO: Write help - "-h", "--h" -> TODO("Help is not implemented yet") + // Define some shorthands... + fun nextArg(): String = ai.nextArgRequired(arg) + fun SetOnce<String>.setNextStringArg(): String = nextArg().also { this.set(it) } + fun SetOnce<String?>.setNextStringArg(): String = nextArg().also { this.set(it) } + fun MutableSet<String>.addUniqueAnnotationArg(): String = + nextArg().also { this += ensureUniqueAnnotation(it) } + + try { + when (arg) { + // TODO: Write help + "-h", "--help" -> TODO("Help is not implemented yet") - "-v", "--verbose" -> ret.logLevel = LogLevel.Verbose - "-d", "--debug" -> ret.logLevel = LogLevel.Debug - "-q", "--quiet" -> ret.logLevel = LogLevel.None + "-v", "--verbose" -> ret.logLevel.set(LogLevel.Verbose) + "-d", "--debug" -> ret.logLevel.set(LogLevel.Debug) + "-q", "--quiet" -> ret.logLevel.set(LogLevel.None) - "--in-jar" -> ret.inJar = ai.nextArgRequired(arg).ensureFileExists() - "--out-stub-jar" -> ret.outStubJar = ai.nextArgRequired(arg) - "--out-impl-jar" -> ret.outImplJar = ai.nextArgRequired(arg) + "--in-jar" -> ret.inJar.setNextStringArg().ensureFileExists() + "--out-stub-jar" -> ret.outStubJar.setNextStringArg() + "--out-impl-jar" -> ret.outImplJar.setNextStringArg() - "--policy-override-file" -> - ret.policyOverrideFile = ai.nextArgRequired(arg).ensureFileExists() + "--policy-override-file" -> + ret.policyOverrideFile.setNextStringArg().ensureFileExists() - "--clean-up-on-error" -> ret.cleanUpOnError = true - "--no-clean-up-on-error" -> ret.cleanUpOnError = false + "--clean-up-on-error" -> ret.cleanUpOnError.set(true) + "--no-clean-up-on-error" -> ret.cleanUpOnError.set(false) - "--default-remove" -> ret.defaultPolicy = FilterPolicy.Remove - "--default-throw" -> ret.defaultPolicy = FilterPolicy.Throw - "--default-keep" -> ret.defaultPolicy = FilterPolicy.Keep - "--default-stub" -> ret.defaultPolicy = FilterPolicy.Stub + "--default-remove" -> ret.defaultPolicy.set(FilterPolicy.Remove) + "--default-throw" -> ret.defaultPolicy.set(FilterPolicy.Throw) + "--default-keep" -> ret.defaultPolicy.set(FilterPolicy.Keep) + "--default-stub" -> ret.defaultPolicy.set(FilterPolicy.Stub) - "--keep-all-classes" -> ret.keepAllClasses = true - "--no-keep-all-classes" -> ret.keepAllClasses = false + "--stub-annotation" -> + ret.stubAnnotations.addUniqueAnnotationArg() - "--stub-annotation" -> - ret.stubAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--keep-annotation" -> + ret.keepAnnotations.addUniqueAnnotationArg() - "--keep-annotation" -> - ret.keepAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--stub-class-annotation" -> + ret.stubClassAnnotations.addUniqueAnnotationArg() - "--stub-class-annotation" -> - ret.stubClassAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--keep-class-annotation" -> + ret.keepClassAnnotations.addUniqueAnnotationArg() - "--keep-class-annotation" -> - ret.keepClassAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--throw-annotation" -> + ret.throwAnnotations.addUniqueAnnotationArg() - "--throw-annotation" -> - ret.throwAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--remove-annotation" -> + ret.removeAnnotations.addUniqueAnnotationArg() - "--remove-annotation" -> - ret.removeAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--substitute-annotation" -> + ret.substituteAnnotations.addUniqueAnnotationArg() - "--substitute-annotation" -> - ret.substituteAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--native-substitute-annotation" -> + ret.nativeSubstituteAnnotations.addUniqueAnnotationArg() - "--native-substitute-annotation" -> - ret.nativeSubstituteAnnotations += - ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--class-load-hook-annotation" -> + ret.classLoadHookAnnotations.addUniqueAnnotationArg() - "--class-load-hook-annotation" -> - ret.classLoadHookAnnotations += - ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--keep-static-initializer-annotation" -> + ret.keepStaticInitializerAnnotations.addUniqueAnnotationArg() - "--keep-static-initializer-annotation" -> - ret.keepStaticInitializerAnnotations += - ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--package-redirect" -> + ret.packageRedirects += parsePackageRedirect(ai.nextArgRequired(arg)) - "--package-redirect" -> - ret.packageRedirects += parsePackageRedirect(ai.nextArgRequired(arg)) + "--annotation-allowed-classes-file" -> + ret.annotationAllowedClassesFile.setNextStringArg() - "--annotation-allowed-classes-file" -> - ret.annotationAllowedClassesFile = ai.nextArgRequired(arg) + "--default-class-load-hook" -> + ret.defaultClassLoadHook.setNextStringArg() - "--default-class-load-hook" -> - ret.defaultClassLoadHook = ai.nextArgRequired(arg) + "--default-method-call-hook" -> + ret.defaultMethodCallHook.setNextStringArg() - "--default-method-call-hook" -> - ret.defaultMethodCallHook = ai.nextArgRequired(arg) + "--intersect-stub-jar" -> + ret.intersectStubJars += nextArg().ensureFileExists() - "--intersect-stub-jar" -> - ret.intersectStubJars += ai.nextArgRequired(arg).ensureFileExists() + "--gen-keep-all-file" -> + ret.inputJarAsKeepAllFile.setNextStringArg() - "--gen-keep-all-file" -> - ret.inputJarAsKeepAllFile = ai.nextArgRequired(arg) + // Following options are for debugging. + "--enable-class-checker" -> ret.enableClassChecker.set(true) + "--no-class-checker" -> ret.enableClassChecker.set(false) - // Following options are for debugging. - "--enable-class-checker" -> ret.enableClassChecker = true - "--no-class-checker" -> ret.enableClassChecker = false + "--enable-pre-trace" -> ret.enablePreTrace.set(true) + "--no-pre-trace" -> ret.enablePreTrace.set(false) - "--enable-pre-trace" -> ret.enablePreTrace = true - "--no-pre-trace" -> ret.enablePreTrace = false + "--enable-post-trace" -> ret.enablePostTrace.set(true) + "--no-post-trace" -> ret.enablePostTrace.set(false) - "--enable-post-trace" -> ret.enablePostTrace = true - "--no-post-trace" -> ret.enablePostTrace = false + "--enable-non-stub-method-check" -> + ret.enableNonStubMethodCallDetection.set(true) - "--enable-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = true - "--no-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = false + "--no-non-stub-method-check" -> + ret.enableNonStubMethodCallDetection.set(false) - "--gen-input-dump-file" -> ret.inputJarDumpFile = ai.nextArgRequired(arg) + "--gen-input-dump-file" -> ret.inputJarDumpFile.setNextStringArg() - else -> throw ArgumentsException("Unknown option: $arg") + else -> throw ArgumentsException("Unknown option: $arg") + } + } catch (e: SetOnce.SetMoreThanOnceException) { + throw ArgumentsException("Duplicate or conflicting argument found: $arg") } } - if (ret.inJar.isEmpty()) { + log.w(ret.toString()) + + if (!ret.inJar.isSet) { throw ArgumentsException("Required option missing: --in-jar") } - if (ret.outStubJar == null && ret.outImplJar == null) { + if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) { log.w("Neither --out-stub-jar nor --out-impl-jar is set." + " $COMMAND_NAME will not generate jar files.") } - if (ret.enableNonStubMethodCallDetection) { + if (ret.enableNonStubMethodCallDetection.get) { log.w("--enable-non-stub-method-check is not fully implemented yet." + " See the todo in doesMethodNeedNonStubCallCheck().") } @@ -329,7 +377,6 @@ class HostStubGenOptions( intersectStubJars=$intersectStubJars, policyOverrideFile=$policyOverrideFile, defaultPolicy=$defaultPolicy, - keepAllClasses=$keepAllClasses, logLevel=$logLevel, cleanUpOnError=$cleanUpOnError, enableClassChecker=$enableClassChecker, diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt index 0321d9db03ed..38ba0ccfd14f 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt @@ -28,9 +28,9 @@ fun main(args: Array<String>) { try { // Parse the command line arguments. val options = HostStubGenOptions.parseArgs(args) - clanupOnError = options.cleanUpOnError + clanupOnError = options.cleanUpOnError.get - log.level = options.logLevel + log.level = options.logLevel.get log.v("HostStubGen started") log.v("Options: $options") diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt index 4df0bfc4a8d1..bc34ef0dc8a7 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.asm import com.android.hoststubgen.ClassParseException @@ -40,6 +55,11 @@ class ClassNodes { return findClass(name) ?: throw ClassParseException("Class $name not found") } + /** @return whether a class exists or not */ + fun hasClass(name: String): Boolean { + return mAllClasses.containsKey(name.toJvmClassName()) + } + /** Find a field, which may not exist. */ fun findField( className: String, diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepAllClassesFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt index 45dd38d10ef5..356e1fa1327c 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepAllClassesFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt @@ -15,16 +15,29 @@ */ package com.android.hoststubgen.filters +import com.android.hoststubgen.asm.ClassNodes + /** - * An [OutputFilter] that keeps all classes by default. (but none of its members) - * - * We're not currently using it, but using it *might* make certain things easier. For example, with - * this, all classes would at least be loadable. + * Filter that deals with Android specific heuristics. */ -class KeepAllClassesFilter(fallback: OutputFilter) : DelegatingFilter(fallback) { +class AndroidHeuristicsFilter( + private val classes: ClassNodes, + val aidlPolicy: FilterPolicyWithReason?, + fallback: OutputFilter +) : DelegatingFilter(fallback) { override fun getPolicyForClass(className: String): FilterPolicyWithReason { - // If the default visibility wouldn't keep it, change it to "keep". - val f = super.getPolicyForClass(className) - return f.promoteToKeep("keep-all-classes") + if (aidlPolicy != null && classes.isAidlClass(className)) { + return aidlPolicy + } + return super.getPolicyForClass(className) } +} + +/** + * @return if a given class "seems like" an AIDL (top-level) class. + */ +private fun ClassNodes.isAidlClass(className: String): Boolean { + return hasClass(className) && + hasClass("$className\$Stub") && + hasClass("$className\$Proxy") }
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt index 416f08505867..b4354ba84e16 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt @@ -58,10 +58,12 @@ fun createFilterFromTextPolicyFile( ): OutputFilter { log.i("Loading offloaded annotations from $filename ...") log.withIndent { - val ret = InMemoryOutputFilter(classes, fallback) + val imf = InMemoryOutputFilter(classes, fallback) var lineNo = 0 + var aidlPolicy: FilterPolicy? = null + try { BufferedReader(FileReader(filename)).use { reader -> var className = "" @@ -79,6 +81,9 @@ fun createFilterFromTextPolicyFile( continue // skip empty lines. } + + // TODO: Method too long, break it up. + val fields = line.split(whitespaceRegex).toTypedArray() when (fields[0].lowercase()) { "c", "class" -> { @@ -86,14 +91,27 @@ fun createFilterFromTextPolicyFile( throw ParseException("Class ('c') expects 2 fields.") } className = fields[1] + + val classType = resolveSpecialClass(className) + if (fields[2].startsWith("!")) { + if (classType != SpecialClass.NotSpecial) { + // We could support it, but not needed at least for now. + throw ParseException( + "Special class can't have a substitution") + } // It's a native-substitution. val toClass = fields[2].substring(1) - ret.setNativeSubstitutionClass(className, toClass) + imf.setNativeSubstitutionClass(className, toClass) } else if (fields[2].startsWith("~")) { + if (classType != SpecialClass.NotSpecial) { + // We could support it, but not needed at least for now. + throw ParseException( + "Special class can't have a class load hook") + } // It's a class-load hook val callback = fields[2].substring(1) - ret.setClassLoadHook(className, callback) + imf.setClassLoadHook(className, callback) } else { val policy = parsePolicy(fields[2]) if (!policy.isUsableWithClasses) { @@ -101,8 +119,20 @@ fun createFilterFromTextPolicyFile( } Objects.requireNonNull(className) - // TODO: Duplicate check, etc - ret.setPolicyForClass(className, policy.withReason(FILTER_REASON)) + when (classType) { + SpecialClass.NotSpecial -> { + // TODO: Duplicate check, etc + imf.setPolicyForClass( + className, policy.withReason(FILTER_REASON)) + } + SpecialClass.Aidl -> { + if (aidlPolicy != null) { + throw ParseException( + "Policy for AIDL classes already defined") + } + aidlPolicy = policy + } + } } } @@ -118,7 +148,7 @@ fun createFilterFromTextPolicyFile( Objects.requireNonNull(className) // TODO: Duplicate check, etc - ret.setPolicyForField(className, name, policy.withReason(FILTER_REASON)) + imf.setPolicyForField(className, name, policy.withReason(FILTER_REASON)) } "m", "method" -> { @@ -135,7 +165,7 @@ fun createFilterFromTextPolicyFile( Objects.requireNonNull(className) - ret.setPolicyForMethod(className, name, signature, + imf.setPolicyForMethod(className, name, signature, policy.withReason(FILTER_REASON)) if (policy.isSubstitute) { val fromName = fields[3].substring(1) @@ -146,12 +176,12 @@ fun createFilterFromTextPolicyFile( } // Set the policy for the "from" method. - ret.setPolicyForMethod(className, fromName, signature, + imf.setPolicyForMethod(className, fromName, signature, policy.getSubstitutionBasePolicy() .withReason(FILTER_REASON)) // Keep "from" -> "to" mapping. - ret.setRenameTo(className, fromName, signature, name) + imf.setRenameTo(className, fromName, signature, name) } } @@ -164,10 +194,32 @@ fun createFilterFromTextPolicyFile( } catch (e: ParseException) { throw e.withSourceInfo(filename, lineNo) } + + var ret: OutputFilter = imf + aidlPolicy?.let { policy -> + log.d("AndroidHeuristicsFilter enabled") + ret = AndroidHeuristicsFilter( + classes, policy.withReason("$FILTER_REASON (AIDL)"), imf) + } return ret } } +private enum class SpecialClass { + NotSpecial, + Aidl, +} + +private fun resolveSpecialClass(className: String): SpecialClass { + if (!className.startsWith(":")) { + return SpecialClass.NotSpecial + } + when (className.lowercase()) { + ":aidl" -> return SpecialClass.Aidl + } + throw ParseException("Invalid special class name \"$className\"") +} + private fun parsePolicy(s: String): FilterPolicy { return when (s.lowercase()) { "s", "stub" -> FilterPolicy.Stub diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index 673d3e8e25e9..214de59bbb4d 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -223,6 +223,103 @@ RuntimeVisibleAnnotations: java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD] ) +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class + Compiled from "IPretendingAidl.java" +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 3 + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy; + + public static int addTwo(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=1, args_size=1 + x: iload_0 + x: iconst_2 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 4 0 a I +} +SourceFile: "IPretendingAidl.java" +NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl +InnerClasses: + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class + Compiled from "IPretendingAidl.java" +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 3 + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub; + + public static int addOne(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=1, args_size=1 + x: iload_0 + x: iconst_1 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 4 0 a I +} +SourceFile: "IPretendingAidl.java" +NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl +InnerClasses: + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class + Compiled from "IPretendingAidl.java" +public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl + minor version: 0 + major version: 61 + flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 0, attributes: 3 +} +SourceFile: "IPretendingAidl.java" +NestMembers: + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub +InnerClasses: + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt index d12588ae4ce2..90312289db1c 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt @@ -1,3 +1,105 @@ +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class + Compiled from "IPretendingAidl.java" +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 4 + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int addTwo(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +InnerClasses: + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class + Compiled from "IPretendingAidl.java" +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 4 + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int addOne(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +InnerClasses: + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class + Compiled from "IPretendingAidl.java" +public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl + minor version: 0 + major version: 61 + flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 0, attributes: 4 +} +InnerClasses: + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestMembers: + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt index 97fb64f38b2d..e01f49baf320 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt @@ -205,6 +205,118 @@ RuntimeVisibleAnnotations: java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class + Compiled from "IPretendingAidl.java" +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 4 + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy; + + public static int addTwo(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=1, args_size=1 + x: iload_0 + x: iconst_2 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 4 0 a I +} +InnerClasses: + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class + Compiled from "IPretendingAidl.java" +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 4 + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub; + + public static int addOne(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=1, args_size=1 + x: iload_0 + x: iconst_1 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 4 0 a I +} +InnerClasses: + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class + Compiled from "IPretendingAidl.java" +public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl + minor version: 0 + major version: 61 + flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 0, attributes: 4 +} +InnerClasses: + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestMembers: + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt index d12588ae4ce2..90312289db1c 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt @@ -1,3 +1,105 @@ +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class + Compiled from "IPretendingAidl.java" +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 4 + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int addTwo(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +InnerClasses: + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class + Compiled from "IPretendingAidl.java" +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 4 + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int addOne(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +InnerClasses: + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class + Compiled from "IPretendingAidl.java" +public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl + minor version: 0 + major version: 61 + flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 0, attributes: 4 +} +InnerClasses: + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestMembers: + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt index 8035189b50c9..5246355fb777 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt @@ -289,6 +289,167 @@ RuntimeVisibleAnnotations: java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class + Compiled from "IPretendingAidl.java" +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 3, attributes: 4 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy; + + public static int addTwo(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + x: ldc #x // String addTwo + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_0 + x: iconst_2 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 4 0 a I +} +InnerClasses: + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class + Compiled from "IPretendingAidl.java" +public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 3, attributes: 4 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub; + + public static int addOne(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub + x: ldc #x // String addOne + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_0 + x: iconst_1 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 4 0 a I +} +InnerClasses: + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl +## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class + Compiled from "IPretendingAidl.java" +public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl + minor version: 0 + major version: 61 + flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT + this_class: #x // com/android/hoststubgen/test/tinyframework/IPretendingAidl + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 1, attributes: 4 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/IPretendingAidl + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return +} +InnerClasses: + public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl + public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl +SourceFile: "IPretendingAidl.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestMembers: + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy + com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt index 079d2a84e498..9b6b6e4c9f5b 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt @@ -15,3 +15,6 @@ class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy stub # Class load hook class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy ~com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded + +# Heuristics rule: Stub all the AIDL classes. +class :aidl stubclass
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java new file mode 100644 index 000000000000..583e13c5573b --- /dev/null +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java @@ -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.hoststubgen.test.tinyframework; + +/** + * An interface that matches the "AIDL detection heuristics' logic. + * + * The "class :aidl" line in the text policy file will control the visibility of it. + */ +public interface IPretendingAidl { + public static class Stub { + public static int addOne(int a) { + return a + 1; + } + } + + public static class Proxy { + public static int addTwo(int a) { + return a + 2; + } + } +} diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java index d04ca52a5682..0d527915ef63 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java @@ -258,7 +258,7 @@ public class TinyFrameworkClassTest { assertThat(TinyFrameworkEnumSimple.valueOf("DOG").ordinal()).isEqualTo(1); assertThat(TinyFrameworkEnumSimple.values()).isEqualTo( - new TinyFrameworkEnumSimple[] { + new TinyFrameworkEnumSimple[]{ TinyFrameworkEnumSimple.CAT, TinyFrameworkEnumSimple.DOG, } @@ -278,11 +278,17 @@ public class TinyFrameworkClassTest { assertThat(TinyFrameworkEnumComplex.valueOf("BLUE").ordinal()).isEqualTo(2); assertThat(TinyFrameworkEnumComplex.values()).isEqualTo( - new TinyFrameworkEnumComplex[] { + new TinyFrameworkEnumComplex[]{ TinyFrameworkEnumComplex.RED, TinyFrameworkEnumComplex.GREEN, TinyFrameworkEnumComplex.BLUE, } ); } + + @Test + public void testAidlHeuristics() { + assertThat(IPretendingAidl.Stub.addOne(1)).isEqualTo(2); + assertThat(IPretendingAidl.Proxy.addTwo(1)).isEqualTo(3); + } } |